tin-spa 20.5.2 → 20.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/tin-spa.mjs +338 -116
- package/fesm2022/tin-spa.mjs.map +1 -1
- package/index.d.ts +191 -9
- package/package.json +1 -1
package/fesm2022/tin-spa.mjs
CHANGED
|
@@ -1482,8 +1482,14 @@ class SignalRService {
|
|
|
1482
1482
|
this.onReconnected = null;
|
|
1483
1483
|
// Changed: Callback for data hub reconnection — tables should reload (set by table components)
|
|
1484
1484
|
this.onDataReconnected = null;
|
|
1485
|
+
// Changed: Observable for data hub connection state — used by real-time table indicators
|
|
1486
|
+
this.dataHubConnected = new BehaviorSubject(false);
|
|
1487
|
+
this.dataHubConnected$ = this.dataHubConnected.asObservable();
|
|
1485
1488
|
}
|
|
1486
1489
|
startConnection(hubUrl, token) {
|
|
1490
|
+
// Changed: Skip if already connected, connecting, or reconnecting to avoid aborting in-progress negotiation
|
|
1491
|
+
if (this.hubConnection && this.hubConnection.state !== signalR.HubConnectionState.Disconnected)
|
|
1492
|
+
return;
|
|
1487
1493
|
if (this.hubConnection)
|
|
1488
1494
|
this.stopConnection();
|
|
1489
1495
|
this.hubConnection = new signalR.HubConnectionBuilder()
|
|
@@ -1503,13 +1509,14 @@ class SignalRService {
|
|
|
1503
1509
|
}
|
|
1504
1510
|
// Changed: Start data hub connection for real-time entity change broadcasts
|
|
1505
1511
|
startDataConnection(hubUrl, token) {
|
|
1506
|
-
if
|
|
1507
|
-
|
|
1512
|
+
// Changed: Skip if already connected, connecting, or reconnecting to avoid aborting in-progress negotiation
|
|
1513
|
+
if (this.dataHubConnection && this.dataHubConnection.state !== signalR.HubConnectionState.Disconnected)
|
|
1514
|
+
return;
|
|
1508
1515
|
if (this.dataHubConnection)
|
|
1509
1516
|
this.stopDataConnection();
|
|
1510
1517
|
this.dataHubConnection = new signalR.HubConnectionBuilder()
|
|
1511
1518
|
.withUrl(hubUrl, { accessTokenFactory: () => token })
|
|
1512
|
-
.withAutomaticReconnect()
|
|
1519
|
+
.withAutomaticReconnect([0, 2000, 5000, 10000, 30000, 60000, 60000, 60000, 60000, 60000]) // Changed: Extended retry policy — keeps trying for ~5 min before giving up
|
|
1513
1520
|
.build();
|
|
1514
1521
|
// Changed: Listen for entity CRUD broadcasts from backend DataHub
|
|
1515
1522
|
this.dataHubConnection.on('EntityCreated', (entityName, data) => {
|
|
@@ -1523,10 +1530,29 @@ class SignalRService {
|
|
|
1523
1530
|
});
|
|
1524
1531
|
// Changed: On data hub reconnect, notify tables to do a full reload (catch missed events)
|
|
1525
1532
|
this.dataHubConnection.onreconnected(() => {
|
|
1533
|
+
this.dataHubConnected.next(true); // Changed: Mark connected on reconnect
|
|
1526
1534
|
if (this.onDataReconnected)
|
|
1527
1535
|
this.onDataReconnected();
|
|
1528
1536
|
});
|
|
1529
|
-
|
|
1537
|
+
// Changed: Track disconnection and reconnecting states for connection indicator
|
|
1538
|
+
this.dataHubConnection.onclose(() => {
|
|
1539
|
+
this.dataHubConnected.next(false);
|
|
1540
|
+
// Changed: If all retries exhausted, keep trying every 60s by restarting the connection
|
|
1541
|
+
setTimeout(() => {
|
|
1542
|
+
if (this.dataHubConnection?.state === signalR.HubConnectionState.Disconnected) {
|
|
1543
|
+
this.dataHubConnection.start()
|
|
1544
|
+
.then(() => this.dataHubConnected.next(true))
|
|
1545
|
+
.catch(() => this.dataHubConnected.next(false));
|
|
1546
|
+
}
|
|
1547
|
+
}, 60000);
|
|
1548
|
+
});
|
|
1549
|
+
this.dataHubConnection.onreconnecting(() => this.dataHubConnected.next(false));
|
|
1550
|
+
this.dataHubConnection.start()
|
|
1551
|
+
.then(() => this.dataHubConnected.next(true)) // Changed: Mark connected on initial start
|
|
1552
|
+
.catch(err => {
|
|
1553
|
+
this.dataHubConnected.next(false);
|
|
1554
|
+
console.error('SignalR data connection error:', err);
|
|
1555
|
+
});
|
|
1530
1556
|
}
|
|
1531
1557
|
stopConnection() {
|
|
1532
1558
|
if (this.hubConnection) {
|
|
@@ -1539,6 +1565,7 @@ class SignalRService {
|
|
|
1539
1565
|
if (this.dataHubConnection) {
|
|
1540
1566
|
this.dataHubConnection.stop();
|
|
1541
1567
|
this.dataHubConnection = null;
|
|
1568
|
+
this.dataHubConnected.next(false); // Changed: Mark disconnected on stop
|
|
1542
1569
|
}
|
|
1543
1570
|
}
|
|
1544
1571
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SignalRService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -1616,10 +1643,10 @@ class AuthService {
|
|
|
1616
1643
|
// Changed: Enhanced clearSession with optional server-side revocation
|
|
1617
1644
|
clearSession(revokeOnServer = false) {
|
|
1618
1645
|
if (revokeOnServer) {
|
|
1619
|
-
// Changed: Revoke refresh
|
|
1620
|
-
const
|
|
1621
|
-
if (
|
|
1622
|
-
this.httpService.Post('User/revoke', {}).subscribe({
|
|
1646
|
+
// Changed: Revoke only this device's refresh token — other devices keep their sessions
|
|
1647
|
+
const refreshToken = this.storage.getPersistent(Constants.AUTH_REFRESH_TOKEN);
|
|
1648
|
+
if (refreshToken) {
|
|
1649
|
+
this.httpService.Post('User/revoke', { refreshToken }).subscribe({
|
|
1623
1650
|
error: () => { } // Silently ignore errors during logout
|
|
1624
1651
|
});
|
|
1625
1652
|
}
|
|
@@ -1763,6 +1790,15 @@ class AuthService {
|
|
|
1763
1790
|
const refreshToken = this.storage.getPersistent(Constants.AUTH_REFRESH_TOKEN);
|
|
1764
1791
|
return refreshToken !== null;
|
|
1765
1792
|
}
|
|
1793
|
+
// Shared entry point for any page that needs to auto-redirect authenticated users
|
|
1794
|
+
// Returns true if session is active or was successfully restored via silent refresh
|
|
1795
|
+
async tryRestoreSession() {
|
|
1796
|
+
if (this.hasValidSession())
|
|
1797
|
+
return true;
|
|
1798
|
+
if (this.hasRefreshToken())
|
|
1799
|
+
return await this.silentRefresh();
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1766
1802
|
// Changed: Silently refresh the JWT using the stored refresh token
|
|
1767
1803
|
silentRefresh() {
|
|
1768
1804
|
if (this.isRefreshing)
|
|
@@ -1783,18 +1819,25 @@ class AuthService {
|
|
|
1783
1819
|
resolve(true);
|
|
1784
1820
|
}
|
|
1785
1821
|
else {
|
|
1786
|
-
//
|
|
1787
|
-
this.storage.
|
|
1788
|
-
|
|
1789
|
-
|
|
1822
|
+
// Changed: Only clear token if it still matches — another tab may have already rotated it
|
|
1823
|
+
const currentToken = this.storage.getPersistent(Constants.AUTH_REFRESH_TOKEN);
|
|
1824
|
+
if (currentToken === refreshToken) {
|
|
1825
|
+
this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN);
|
|
1826
|
+
this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN_EXPIRE);
|
|
1827
|
+
this.storage.removePersistent(Constants.AUTH_REMEMBER_ME);
|
|
1828
|
+
}
|
|
1790
1829
|
resolve(false);
|
|
1791
1830
|
}
|
|
1792
1831
|
},
|
|
1793
1832
|
error: () => {
|
|
1794
1833
|
this.isRefreshing = false;
|
|
1795
|
-
|
|
1796
|
-
this.storage.
|
|
1797
|
-
|
|
1834
|
+
// Changed: Only clear token if it still matches — another tab may have already rotated it
|
|
1835
|
+
const currentToken = this.storage.getPersistent(Constants.AUTH_REFRESH_TOKEN);
|
|
1836
|
+
if (currentToken === refreshToken) {
|
|
1837
|
+
this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN);
|
|
1838
|
+
this.storage.removePersistent(Constants.AUTH_REFRESH_TOKEN_EXPIRE);
|
|
1839
|
+
this.storage.removePersistent(Constants.AUTH_REMEMBER_ME);
|
|
1840
|
+
}
|
|
1798
1841
|
resolve(false);
|
|
1799
1842
|
}
|
|
1800
1843
|
});
|
|
@@ -2451,7 +2494,9 @@ class DataServiceLib {
|
|
|
2451
2494
|
],
|
|
2452
2495
|
loadAction: { url: 'suppliers/all/x' },
|
|
2453
2496
|
formConfig: this.supplierFormConfig,
|
|
2454
|
-
|
|
2497
|
+
allowUserKeepOpen: true, // Added: for testing — propagates to Create dialog (which has no detailsConfig of its own)
|
|
2498
|
+
keepOpenBehavior: 'reset', // Added: for testing — reset form after create so user can capture next supplier
|
|
2499
|
+
realTime: true, // Changed: Enabled for SignalR testing
|
|
2455
2500
|
entityName: 'Supplier' // Changed: Match backend entity name for SignalR broadcasts
|
|
2456
2501
|
};
|
|
2457
2502
|
//--------------------------GPT Caches-------------------------
|
|
@@ -4047,7 +4092,9 @@ class AccountingService {
|
|
|
4047
4092
|
]
|
|
4048
4093
|
},
|
|
4049
4094
|
{ name: 'errorMessage', type: 'text', alias: 'Error',
|
|
4050
|
-
colors: [
|
|
4095
|
+
colors: [
|
|
4096
|
+
{ name: 'red', condition: (x) => !!x.errorMessage }
|
|
4097
|
+
]
|
|
4051
4098
|
}
|
|
4052
4099
|
],
|
|
4053
4100
|
buttons: [
|
|
@@ -4110,7 +4157,7 @@ class AccountingService {
|
|
|
4110
4157
|
title: 'Financial Transaction',
|
|
4111
4158
|
fixedTitle: true,
|
|
4112
4159
|
fields: [
|
|
4113
|
-
{ name: 'transactionTypeID', type: '
|
|
4160
|
+
{ name: 'transactionTypeID', type: 'text-single', alias: 'Transaction Type', required: true, detailsConfig: this.transactionTypeDetailsConfig,
|
|
4114
4161
|
loadAction: { url: 'transactionTypes/list/x' },
|
|
4115
4162
|
},
|
|
4116
4163
|
{ name: 'date', type: 'date', required: true },
|
|
@@ -4175,7 +4222,9 @@ class AccountingService {
|
|
|
4175
4222
|
loadAction: { url: 'transactions/all/x' },
|
|
4176
4223
|
formConfig: this.transactionFormConfig,
|
|
4177
4224
|
realTime: true,
|
|
4178
|
-
entityName: 'Transaction'
|
|
4225
|
+
entityName: 'Transaction',
|
|
4226
|
+
allowUserKeepOpen: true, // Added: shows Keep Open checkbox on transaction dialogs
|
|
4227
|
+
keepOpenBehavior: 'reset', // Added: reset form after create so user can capture next transaction
|
|
4179
4228
|
};
|
|
4180
4229
|
// Changed: Search mode config for transactions — date range, description contains, account filter
|
|
4181
4230
|
this.transactionSearchTableConfig = {
|
|
@@ -4186,7 +4235,7 @@ class AccountingService {
|
|
|
4186
4235
|
{ name: 'dateTo', type: 'date', alias: 'To Date', show: true },
|
|
4187
4236
|
{ name: 'description', type: 'text', alias: 'Description', show: true },
|
|
4188
4237
|
{ name: 'accountID', type: 'select', alias: 'Account', show: true, loadAction: { url: 'accounts/list/x' } },
|
|
4189
|
-
{ name: 'typeName', type: '
|
|
4238
|
+
{ name: 'typeName', type: 'text-single', alias: 'Transaction Type', show: true, loadAction: { url: 'transactiontypes/list/x' } }, // Changed: text-single for type-to-search autocomplete
|
|
4190
4239
|
],
|
|
4191
4240
|
searchAction: { url: 'transactions/search', method: 'post' }
|
|
4192
4241
|
}
|
|
@@ -7103,8 +7152,11 @@ class TextSingleComponent {
|
|
|
7103
7152
|
}
|
|
7104
7153
|
ngOnChanges(changes) {
|
|
7105
7154
|
if (changes['value'] && this.myControl) {
|
|
7106
|
-
|
|
7107
|
-
|
|
7155
|
+
const newDisplay = this.getDisplayValue(changes['value'].currentValue);
|
|
7156
|
+
if (this.myControl.value !== newDisplay) {
|
|
7157
|
+
// Changed: Emit event when value is cleared so filteredOptions pipe re-evaluates and shows all options
|
|
7158
|
+
const wasCleared = !changes['value'].currentValue;
|
|
7159
|
+
this.myControl.setValue(newDisplay, { emitEvent: wasCleared });
|
|
7108
7160
|
}
|
|
7109
7161
|
}
|
|
7110
7162
|
if (changes['readonly'] && this.myControl) {
|
|
@@ -10760,6 +10812,7 @@ class DetailsDialogLite {
|
|
|
10760
10812
|
this.authService = authService;
|
|
10761
10813
|
this.tableConfigService = tableConfigService;
|
|
10762
10814
|
this.autoRefreshEnabled = false;
|
|
10815
|
+
this.userKeepOpen = false; // Added: runtime state for the user-controlled Keep Open checkbox
|
|
10763
10816
|
this.titleAction = "View";
|
|
10764
10817
|
this.loadByAction = false;
|
|
10765
10818
|
this.files = [];
|
|
@@ -10983,9 +11036,15 @@ class DetailsDialogLite {
|
|
|
10983
11036
|
}
|
|
10984
11037
|
this.processButtonAction(button);
|
|
10985
11038
|
}
|
|
11039
|
+
// Added: returns the effective keepOpen value — user checkbox takes precedence when allowUserKeepOpen is enabled
|
|
11040
|
+
effectiveKeepOpen(button) {
|
|
11041
|
+
if (this.detailsConfig.allowUserKeepOpen)
|
|
11042
|
+
return this.userKeepOpen;
|
|
11043
|
+
return button.keepOpen ?? false;
|
|
11044
|
+
}
|
|
10986
11045
|
processButtonAction(button) {
|
|
10987
11046
|
if (!button.action) {
|
|
10988
|
-
if (!button
|
|
11047
|
+
if (!this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
|
|
10989
11048
|
this.dialogRef.close({ message: 'emit', data: this.details });
|
|
10990
11049
|
}
|
|
10991
11050
|
return;
|
|
@@ -11065,8 +11124,12 @@ class DetailsDialogLite {
|
|
|
11065
11124
|
if (button.onSuccess) {
|
|
11066
11125
|
button.onSuccess(apiResponse.data, this.details);
|
|
11067
11126
|
}
|
|
11068
|
-
if (button
|
|
11069
|
-
if (button.name === 'create' &&
|
|
11127
|
+
if (this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
|
|
11128
|
+
if (button.name === 'create' && this.detailsConfig.allowUserKeepOpen && this.userKeepOpen && this.detailsConfig.keepOpenBehavior === 'reset') {
|
|
11129
|
+
// Changed: Reset values in-place to preserve loaded select options for next capture
|
|
11130
|
+
Core.resetObject(this.formConfig.fields, this.details);
|
|
11131
|
+
}
|
|
11132
|
+
else if (button.name === 'create' && apiResponse.data) {
|
|
11070
11133
|
this.details = apiResponse.data;
|
|
11071
11134
|
if (this.detailsConfig.heroField && apiResponse.data[this.detailsConfig.heroField]) {
|
|
11072
11135
|
this.detailsConfig.heroValue = apiResponse.data[this.detailsConfig.heroField];
|
|
@@ -11111,11 +11174,11 @@ class DetailsDialogLite {
|
|
|
11111
11174
|
}
|
|
11112
11175
|
}
|
|
11113
11176
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialogLite, deps: [{ token: i1$3.BreakpointObserver }, { token: LoaderService }, { token: DataServiceLib }, { token: MessageService }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: ButtonService }, { token: DialogService }, { token: AuthService }, { token: TableConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11114
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialogLite, isStandalone: false, selector: "spa-detailsDialog-lite", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-lite\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n \r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-lite>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- \r\n Lite Dialog should not implement tabs \r\n PlaceHolder\r\n \r\n \r\n \r\n \r\n \r\n -->\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
11177
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialogLite, isStandalone: false, selector: "spa-detailsDialog-lite", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-lite\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n \r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-lite>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- \r\n Lite Dialog should not implement tabs \r\n PlaceHolder\r\n \r\n \r\n \r\n \r\n \r\n -->\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: SelectLiteComponent, selector: "spa-select-lite" }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
11115
11178
|
}
|
|
11116
11179
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialogLite, decorators: [{
|
|
11117
11180
|
type: Component,
|
|
11118
|
-
args: [{ selector: 'spa-detailsDialog-lite', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <
|
|
11181
|
+
args: [{ selector: 'spa-detailsDialog-lite', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-lite\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n \r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-lite>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- \r\n Lite Dialog should not implement tabs \r\n PlaceHolder\r\n \r\n \r\n \r\n \r\n \r\n -->\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
11119
11182
|
}], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: LoaderService }, { type: DataServiceLib }, { type: MessageService }, { type: i1.MatDialogRef }, { type: DetailsDialogConfig, decorators: [{
|
|
11120
11183
|
type: Inject,
|
|
11121
11184
|
args: [MAT_DIALOG_DATA]
|
|
@@ -11298,6 +11361,8 @@ class TableHeaderComponent {
|
|
|
11298
11361
|
this.csvService = csvService;
|
|
11299
11362
|
this.data = [];
|
|
11300
11363
|
this.tileData = [];
|
|
11364
|
+
this.isRealTime = false; // Changed: Whether this table uses real-time SignalR updates
|
|
11365
|
+
this.isConnected = false; // Changed: Whether the SignalR data hub is currently connected
|
|
11301
11366
|
this.createClick = new EventEmitter();
|
|
11302
11367
|
this.customClick = new EventEmitter();
|
|
11303
11368
|
this.refreshClick = new EventEmitter();
|
|
@@ -11387,11 +11452,11 @@ class TableHeaderComponent {
|
|
|
11387
11452
|
return this.buttonService.getButtonColor(button, row);
|
|
11388
11453
|
}
|
|
11389
11454
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableHeaderComponent, deps: [{ token: ButtonService }, { token: DialogService }, { token: MessageService }, { token: CsvService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11390
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableHeaderComponent, isStandalone: false, selector: "app-table-header", inputs: { lastSearch: "lastSearch", config: "config", hideTitle: "hideTitle", tableDataSource: "tableDataSource", tileConfig: "tileConfig", smallScreen: "smallScreen", tileReload: "tileReload", showFilterButton: "showFilterButton", data: "data", tileData: "tileData" }, outputs: { createClick: "createClick", customClick: "customClick", refreshClick: "refreshClick", tileClick: "tileClick", tileUnClick: "tileUnClick" }, ngImport: i0, template: "<!--Tiles Top Position -->\r\n<ng-container *ngIf=\"config.tileConfig && !smallScreen && config.tileConfig.headerPosition == 'top'\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</ng-container>\r\n\r\n<!-- Changed: Small screen tiles render above header buttons/filter -->\r\n<div *ngIf=\"config.tileConfig && smallScreen\" style=\"width: 100%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</div>\r\n\r\n<div class=\"top\">\r\n\r\n <!-- buttons -->\r\n <div class=\"tin-row\" style=\"margin-right:
|
|
11455
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableHeaderComponent, isStandalone: false, selector: "app-table-header", inputs: { lastSearch: "lastSearch", config: "config", hideTitle: "hideTitle", tableDataSource: "tableDataSource", tileConfig: "tileConfig", smallScreen: "smallScreen", tileReload: "tileReload", showFilterButton: "showFilterButton", data: "data", tileData: "tileData", isRealTime: "isRealTime", isConnected: "isConnected" }, outputs: { createClick: "createClick", customClick: "customClick", refreshClick: "refreshClick", tileClick: "tileClick", tileUnClick: "tileUnClick" }, ngImport: i0, template: "<!--Tiles Top Position -->\r\n<ng-container *ngIf=\"config.tileConfig && !smallScreen && config.tileConfig.headerPosition == 'top'\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</ng-container>\r\n\r\n<!-- Changed: Small screen tiles render above header buttons/filter -->\r\n<div *ngIf=\"config.tileConfig && smallScreen\" style=\"width: 100%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</div>\r\n\r\n<div class=\"top\">\r\n\r\n <!-- Changed: Dot + buttons in a single flex row so they cascade together -->\r\n <div class=\"tin-row d-flex align-items-center\" style=\"margin-right: 5px;\">\r\n\r\n <!-- Changed: Real-time connection indicator \u2014 first item, with left padding for alignment -->\r\n <div *ngIf=\"isRealTime\" class=\"d-flex align-items-center\" style=\"padding-left: 10px;\"\r\n [matTooltip]=\"isConnected ? 'Live \u2014 connected to server' : 'Offline \u2014 not connected'\" matTooltipPosition=\"above\">\r\n <span style=\"display: inline-block; width: 10px; height: 10px; border-radius: 50%;\"\r\n [style.background-color]=\"isConnected ? '#4caf50' : '#9e9e9e'\"></span>\r\n </div>\r\n\r\n <ng-container *ngFor=\"let button of getHeaderButtons()\">\r\n <ng-container *ngIf=\"testVisible(button)\" >\r\n <button *ngIf=\"!config.flatButtons\" mat-raised-button color=\"primary\" [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" >\r\n {{button.display}}\r\n </button>\r\n <button *ngIf=\"config.flatButtons\" mat-stroked-button [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" [ngStyle]=\"{'color': getButtonColor(button, config.parentData)}\">\r\n {{button.display}}\r\n </button>\r\n </ng-container>\r\n </ng-container>\r\n\r\n </div>\r\n <div *ngIf=\"!isRealTime && getHeaderButtons().length == 0 && config.holdHeaderButtonSpace || !isRealTime && getHeaderButtons().length == 0 && !config.tileConfig\"></div>\r\n\r\n <!-- tiles -->\r\n <div *ngIf=\"config.tileConfig && !smallScreen && (!config.tileConfig.headerPosition || config.tileConfig.headerPosition == 'middle')\" style=\"min-width: 75%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n </div>\r\n\r\n <!-- filter -->\r\n <div class=\"tin-row d-flex justify-content-end\" >\r\n\r\n <button *ngIf=\"config.download && testVisibleHeaderButton(config.download)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Download\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onDownloadClick()\">\r\n <mat-icon>download</mat-icon>\r\n </button>\r\n\r\n <button *ngIf=\"config.upload && testVisibleHeaderButton(config.upload)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Upload\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onUploadClick()\">\r\n <mat-icon>upload</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"config.showFilter\" >\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.data?.length > 10)\" [showButton]=\"showFilterButton\" [data]=\"tableDataSource\" [flatButtons]=\"config.flatButtons\" [smallScreen]=\"smallScreen\" (refreshClick)=\"onRefreshClick()\"></spa-filter> <!-- Changed: Use .data.length for MatTableDataSource, pass smallScreen for compact width -->\r\n </div>\r\n <div *ngIf=\"!config.showFilter && config.holdFilterSpace\"></div>\r\n </div>\r\n\r\n\r\n</div>\r\n\r\n<!-- <div *ngIf=\"config.title && !hideTitle\" class=\"title\">\r\n {{config.title | camelToWords}}\r\n</div> -->\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;row-gap:8px;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.header-action-btn{width:20px!important;height:20px!important}.header-action-btn mat-icon{font-size:18px!important;margin-top:0!important}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.tin-row{display:flex;align-items:center}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FilterComponent, selector: "spa-filter", inputs: ["flatButtons", "showText", "showButton", "smallScreen", "data"], outputs: ["refreshClick"] }, { kind: "component", type: TilesComponent, selector: "spa-tiles", inputs: ["config", "lastSearch", "data", "reload"], outputs: ["tileActionSelected", "tileClick", "tileUnClick"] }] }); }
|
|
11391
11456
|
}
|
|
11392
11457
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableHeaderComponent, decorators: [{
|
|
11393
11458
|
type: Component,
|
|
11394
|
-
args: [{ selector: 'app-table-header', standalone: false, template: "<!--Tiles Top Position -->\r\n<ng-container *ngIf=\"config.tileConfig && !smallScreen && config.tileConfig.headerPosition == 'top'\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</ng-container>\r\n\r\n<!-- Changed: Small screen tiles render above header buttons/filter -->\r\n<div *ngIf=\"config.tileConfig && smallScreen\" style=\"width: 100%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</div>\r\n\r\n<div class=\"top\">\r\n\r\n <!-- buttons -->\r\n <div class=\"tin-row\" style=\"margin-right:
|
|
11459
|
+
args: [{ selector: 'app-table-header', standalone: false, template: "<!--Tiles Top Position -->\r\n<ng-container *ngIf=\"config.tileConfig && !smallScreen && config.tileConfig.headerPosition == 'top'\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</ng-container>\r\n\r\n<!-- Changed: Small screen tiles render above header buttons/filter -->\r\n<div *ngIf=\"config.tileConfig && smallScreen\" style=\"width: 100%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n</div>\r\n\r\n<div class=\"top\">\r\n\r\n <!-- Changed: Dot + buttons in a single flex row so they cascade together -->\r\n <div class=\"tin-row d-flex align-items-center\" style=\"margin-right: 5px;\">\r\n\r\n <!-- Changed: Real-time connection indicator \u2014 first item, with left padding for alignment -->\r\n <div *ngIf=\"isRealTime\" class=\"d-flex align-items-center\" style=\"padding-left: 10px;\"\r\n [matTooltip]=\"isConnected ? 'Live \u2014 connected to server' : 'Offline \u2014 not connected'\" matTooltipPosition=\"above\">\r\n <span style=\"display: inline-block; width: 10px; height: 10px; border-radius: 50%;\"\r\n [style.background-color]=\"isConnected ? '#4caf50' : '#9e9e9e'\"></span>\r\n </div>\r\n\r\n <ng-container *ngFor=\"let button of getHeaderButtons()\">\r\n <ng-container *ngIf=\"testVisible(button)\" >\r\n <button *ngIf=\"!config.flatButtons\" mat-raised-button color=\"primary\" [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" >\r\n {{button.display}}\r\n </button>\r\n <button *ngIf=\"config.flatButtons\" mat-stroked-button [disabled]=\"testDisabled(button)\" (click)=\"onButtonClick(button)\" [ngStyle]=\"{'color': getButtonColor(button, config.parentData)}\">\r\n {{button.display}}\r\n </button>\r\n </ng-container>\r\n </ng-container>\r\n\r\n </div>\r\n <div *ngIf=\"!isRealTime && getHeaderButtons().length == 0 && config.holdHeaderButtonSpace || !isRealTime && getHeaderButtons().length == 0 && !config.tileConfig\"></div>\r\n\r\n <!-- tiles -->\r\n <div *ngIf=\"config.tileConfig && !smallScreen && (!config.tileConfig.headerPosition || config.tileConfig.headerPosition == 'middle')\" style=\"min-width: 75%;\">\r\n <spa-tiles [config]=\"config.tileConfig\" [reload]=\"tileReload\" [data]=\"tileData\" [lastSearch]=\"lastSearch\" (tileClick)=\"onTileClick($event)\" (tileUnClick)=\"onTileUnClick($event)\"></spa-tiles>\r\n </div>\r\n\r\n <!-- filter -->\r\n <div class=\"tin-row d-flex justify-content-end\" >\r\n\r\n <button *ngIf=\"config.download && testVisibleHeaderButton(config.download)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Download\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onDownloadClick()\">\r\n <mat-icon>download</mat-icon>\r\n </button>\r\n\r\n <button *ngIf=\"config.upload && testVisibleHeaderButton(config.upload)\" mat-icon-button class=\"header-action-btn\" matTooltip=\"Upload\" matTooltipPosition=\"above\" color=\"primary\" (click)=\"onUploadClick()\">\r\n <mat-icon>upload</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"config.showFilter\" >\r\n <spa-filter [showText]=\"!smallScreen || (smallScreen && tableDataSource?.data?.length > 10)\" [showButton]=\"showFilterButton\" [data]=\"tableDataSource\" [flatButtons]=\"config.flatButtons\" [smallScreen]=\"smallScreen\" (refreshClick)=\"onRefreshClick()\"></spa-filter> <!-- Changed: Use .data.length for MatTableDataSource, pass smallScreen for compact width -->\r\n </div>\r\n <div *ngIf=\"!config.showFilter && config.holdFilterSpace\"></div>\r\n </div>\r\n\r\n\r\n</div>\r\n\r\n<!-- <div *ngIf=\"config.title && !hideTitle\" class=\"title\">\r\n {{config.title | camelToWords}}\r\n</div> -->\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;row-gap:8px;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.header-action-btn{width:20px!important;height:20px!important}.header-action-btn mat-icon{font-size:18px!important;margin-top:0!important}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#e5e5e5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.tin-row{display:flex;align-items:center}\n"] }]
|
|
11395
11460
|
}], ctorParameters: () => [{ type: ButtonService }, { type: DialogService }, { type: MessageService }, { type: CsvService }], propDecorators: { lastSearch: [{
|
|
11396
11461
|
type: Input
|
|
11397
11462
|
}], config: [{
|
|
@@ -11412,6 +11477,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
11412
11477
|
type: Input
|
|
11413
11478
|
}], tileData: [{
|
|
11414
11479
|
type: Input
|
|
11480
|
+
}], isRealTime: [{
|
|
11481
|
+
type: Input
|
|
11482
|
+
}], isConnected: [{
|
|
11483
|
+
type: Input
|
|
11415
11484
|
}], createClick: [{
|
|
11416
11485
|
type: Output
|
|
11417
11486
|
}], customClick: [{
|
|
@@ -12257,12 +12326,16 @@ class TableLiteComponent {
|
|
|
12257
12326
|
const buttonName = button.name;
|
|
12258
12327
|
let btn = {
|
|
12259
12328
|
...button,
|
|
12260
|
-
detailsConfig:
|
|
12261
|
-
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12329
|
+
detailsConfig: {
|
|
12330
|
+
...(button.detailsConfig ?? {
|
|
12331
|
+
formConfig: this.config.formConfig,
|
|
12332
|
+
stepConfig: this.config.stepConfig,
|
|
12333
|
+
buttons: this.config.buttons,
|
|
12334
|
+
heroField: this.config.heroField,
|
|
12335
|
+
heroValue: this.config.heroValue,
|
|
12336
|
+
}),
|
|
12337
|
+
allowUserKeepOpen: this.config.allowUserKeepOpen, // Changed: table config is the single source of truth for this setting
|
|
12338
|
+
keepOpenBehavior: this.config.keepOpenBehavior, // Changed: propagate create behavior preference from table config
|
|
12266
12339
|
}
|
|
12267
12340
|
};
|
|
12268
12341
|
this.dialogService.openConfiguredDetailsDialog(btn, row, DetailsDialogLite).subscribe(result => {
|
|
@@ -12372,7 +12445,7 @@ class TableLiteComponent {
|
|
|
12372
12445
|
}
|
|
12373
12446
|
}
|
|
12374
12447
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableLiteComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
12375
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableLiteComponent, isStandalone: false, selector: "spa-table-lite", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
12448
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableLiteComponent, isStandalone: false, selector: "spa-table-lite", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData", "isRealTime", "isConnected"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
12376
12449
|
}
|
|
12377
12450
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableLiteComponent, decorators: [{
|
|
12378
12451
|
type: Component,
|
|
@@ -12512,6 +12585,7 @@ class DetailsDialogInternal {
|
|
|
12512
12585
|
this.authService = authService;
|
|
12513
12586
|
this.tableConfigService = tableConfigService;
|
|
12514
12587
|
this.autoRefreshEnabled = false;
|
|
12588
|
+
this.userKeepOpen = false; // Added: runtime state for the user-controlled Keep Open checkbox
|
|
12515
12589
|
this.titleAction = "View";
|
|
12516
12590
|
this.loadByAction = false;
|
|
12517
12591
|
this.files = [];
|
|
@@ -12727,9 +12801,15 @@ class DetailsDialogInternal {
|
|
|
12727
12801
|
}
|
|
12728
12802
|
this.processButtonAction(button);
|
|
12729
12803
|
}
|
|
12804
|
+
// Added: returns the effective keepOpen value — user checkbox takes precedence when allowUserKeepOpen is enabled
|
|
12805
|
+
effectiveKeepOpen(button) {
|
|
12806
|
+
if (this.detailsConfig.allowUserKeepOpen)
|
|
12807
|
+
return this.userKeepOpen;
|
|
12808
|
+
return button.keepOpen ?? false;
|
|
12809
|
+
}
|
|
12730
12810
|
processButtonAction(button) {
|
|
12731
12811
|
if (!button.action) {
|
|
12732
|
-
if (!button
|
|
12812
|
+
if (!this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
|
|
12733
12813
|
this.dialogRef.close({ message: 'emit', data: this.details });
|
|
12734
12814
|
}
|
|
12735
12815
|
return;
|
|
@@ -12809,8 +12889,12 @@ class DetailsDialogInternal {
|
|
|
12809
12889
|
if (button.onSuccess) {
|
|
12810
12890
|
button.onSuccess(apiResponse.data, this.details);
|
|
12811
12891
|
}
|
|
12812
|
-
if (button
|
|
12813
|
-
if (button.name === 'create' &&
|
|
12892
|
+
if (this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
|
|
12893
|
+
if (button.name === 'create' && this.detailsConfig.allowUserKeepOpen && this.userKeepOpen && this.detailsConfig.keepOpenBehavior === 'reset') {
|
|
12894
|
+
// Changed: Reset values in-place to preserve loaded select options for next capture
|
|
12895
|
+
Core.resetObject(this.formConfig.fields, this.details);
|
|
12896
|
+
}
|
|
12897
|
+
else if (button.name === 'create' && apiResponse.data) {
|
|
12814
12898
|
this.details = apiResponse.data;
|
|
12815
12899
|
if (this.detailsConfig.heroField && apiResponse.data[this.detailsConfig.heroField]) {
|
|
12816
12900
|
this.detailsConfig.heroValue = apiResponse.data[this.detailsConfig.heroField];
|
|
@@ -12855,11 +12939,11 @@ class DetailsDialogInternal {
|
|
|
12855
12939
|
}
|
|
12856
12940
|
}
|
|
12857
12941
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialogInternal, deps: [{ token: i1$3.BreakpointObserver }, { token: LoaderService }, { token: DataServiceLib }, { token: MessageService }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: ButtonService }, { token: DialogService }, { token: AuthService }, { token: TableConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
12858
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialogInternal, isStandalone: false, selector: "spa-detailsDialog-internal", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-internal\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-internal>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-lite component -->\r\n <spa-tabs-lite \r\n *ngIf=\"tableConfigs\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-lite>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: SelectInternalComponent, selector: "spa-select-internal", inputs: ["detailsConfig"] }, { kind: "component", type: TabsLiteComponent, selector: "spa-tabs-lite", inputs: ["tableConfigs", "reload", "parentDetails"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
12942
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialogInternal, isStandalone: false, selector: "spa-detailsDialog-internal", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-internal\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-internal>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-lite component -->\r\n <spa-tabs-lite \r\n *ngIf=\"tableConfigs\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-lite>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: SelectInternalComponent, selector: "spa-select-internal", inputs: ["detailsConfig"] }, { kind: "component", type: TabsLiteComponent, selector: "spa-tabs-lite", inputs: ["tableConfigs", "reload", "parentDetails"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
12859
12943
|
}
|
|
12860
12944
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialogInternal, decorators: [{
|
|
12861
12945
|
type: Component,
|
|
12862
|
-
args: [{ selector: 'spa-detailsDialog-internal', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <
|
|
12946
|
+
args: [{ selector: 'spa-detailsDialog-internal', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select-internal\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select-internal>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-lite component -->\r\n <spa-tabs-lite \r\n *ngIf=\"tableConfigs\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-lite>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions>\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\" \r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\" \r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
12863
12947
|
}], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: LoaderService }, { type: DataServiceLib }, { type: MessageService }, { type: i1.MatDialogRef }, { type: DetailsDialogConfig, decorators: [{
|
|
12864
12948
|
type: Inject,
|
|
12865
12949
|
args: [MAT_DIALOG_DATA]
|
|
@@ -13172,12 +13256,16 @@ class TableInternalComponent {
|
|
|
13172
13256
|
const buttonName = button.name;
|
|
13173
13257
|
let btn = {
|
|
13174
13258
|
...button,
|
|
13175
|
-
detailsConfig:
|
|
13176
|
-
|
|
13177
|
-
|
|
13178
|
-
|
|
13179
|
-
|
|
13180
|
-
|
|
13259
|
+
detailsConfig: {
|
|
13260
|
+
...(button.detailsConfig ?? {
|
|
13261
|
+
formConfig: this.config.formConfig,
|
|
13262
|
+
stepConfig: this.config.stepConfig,
|
|
13263
|
+
buttons: this.config.buttons,
|
|
13264
|
+
heroField: this.config.heroField,
|
|
13265
|
+
heroValue: this.config.heroValue,
|
|
13266
|
+
}),
|
|
13267
|
+
allowUserKeepOpen: this.config.allowUserKeepOpen, // Changed: table config is the single source of truth for this setting
|
|
13268
|
+
keepOpenBehavior: this.config.keepOpenBehavior, // Changed: propagate create behavior preference from table config
|
|
13181
13269
|
}
|
|
13182
13270
|
};
|
|
13183
13271
|
this.dialogService.openConfiguredDetailsDialog(btn, row, DetailsDialogInternal).subscribe(result => {
|
|
@@ -13361,7 +13449,7 @@ class TableInternalComponent {
|
|
|
13361
13449
|
this.realTimeSubs.forEach(s => s.unsubscribe());
|
|
13362
13450
|
}
|
|
13363
13451
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableInternalComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }, { token: SignalRService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
13364
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableInternalComponent, isStandalone: false, selector: "spa-table-internal", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
13452
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableInternalComponent, isStandalone: false, selector: "spa-table-internal", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '50px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData", "isRealTime", "isConnected"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
13365
13453
|
}
|
|
13366
13454
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableInternalComponent, decorators: [{
|
|
13367
13455
|
type: Component,
|
|
@@ -13501,6 +13589,7 @@ class DetailsDialog {
|
|
|
13501
13589
|
this.authService = authService;
|
|
13502
13590
|
this.tableConfigService = tableConfigService;
|
|
13503
13591
|
this.autoRefreshEnabled = false;
|
|
13592
|
+
this.userKeepOpen = false; // Added: runtime state for the user-controlled Keep Open checkbox
|
|
13504
13593
|
this.titleAction = "View";
|
|
13505
13594
|
this.loadByAction = false;
|
|
13506
13595
|
this.files = [];
|
|
@@ -13733,9 +13822,15 @@ class DetailsDialog {
|
|
|
13733
13822
|
}
|
|
13734
13823
|
this.processButtonAction(button);
|
|
13735
13824
|
}
|
|
13825
|
+
// Added: returns the effective keepOpen value — user checkbox takes precedence when allowUserKeepOpen is enabled
|
|
13826
|
+
effectiveKeepOpen(button) {
|
|
13827
|
+
if (this.detailsConfig.allowUserKeepOpen)
|
|
13828
|
+
return this.userKeepOpen;
|
|
13829
|
+
return button.keepOpen ?? false;
|
|
13830
|
+
}
|
|
13736
13831
|
processButtonAction(button) {
|
|
13737
13832
|
if (!button.action) {
|
|
13738
|
-
if (!button
|
|
13833
|
+
if (!this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
|
|
13739
13834
|
this.dialogRef.close({ message: 'emit', data: this.details });
|
|
13740
13835
|
}
|
|
13741
13836
|
return;
|
|
@@ -13815,15 +13910,19 @@ class DetailsDialog {
|
|
|
13815
13910
|
if (button.onSuccess) {
|
|
13816
13911
|
button.onSuccess(apiResponse.data, this.details);
|
|
13817
13912
|
}
|
|
13818
|
-
if (button
|
|
13913
|
+
if (this.effectiveKeepOpen(button)) { // Changed: use effectiveKeepOpen to respect user checkbox
|
|
13819
13914
|
if (button.resetMode) {
|
|
13820
|
-
// Changed: Reset
|
|
13821
|
-
|
|
13915
|
+
// Changed: Reset values in-place to preserve loaded select options
|
|
13916
|
+
Core.resetObject(this.formConfig.fields, this.details);
|
|
13822
13917
|
this.formConfig.mode = button.resetMode;
|
|
13823
13918
|
this.loadByAction = false;
|
|
13824
13919
|
this.isLoadComplete = true;
|
|
13825
13920
|
this.setTitleAction();
|
|
13826
13921
|
}
|
|
13922
|
+
else if (button.name === 'create' && this.detailsConfig.allowUserKeepOpen && this.userKeepOpen && this.detailsConfig.keepOpenBehavior === 'reset') {
|
|
13923
|
+
// Changed: Reset values in-place to preserve loaded select options for next capture
|
|
13924
|
+
Core.resetObject(this.formConfig.fields, this.details);
|
|
13925
|
+
}
|
|
13827
13926
|
else if (button.name === 'create' && apiResponse.data) {
|
|
13828
13927
|
this.details = apiResponse.data;
|
|
13829
13928
|
if (this.detailsConfig.heroField && apiResponse.data[this.detailsConfig.heroField]) {
|
|
@@ -13884,11 +13983,11 @@ class DetailsDialog {
|
|
|
13884
13983
|
}
|
|
13885
13984
|
}
|
|
13886
13985
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialog, deps: [{ token: i1$3.BreakpointObserver }, { token: LoaderService }, { token: DataServiceLib }, { token: MessageService }, { token: i1.MatDialogRef }, { token: MAT_DIALOG_DATA }, { token: ButtonService }, { token: DialogService }, { token: AuthService }, { token: TableConfigService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
13887
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialog, isStandalone: false, selector: "spa-detailsDialog", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <spa-check *ngIf=\"detailsConfig.autoRefreshConfig\" display=\"Auto Refresh\" [(value)]=\"autoRefreshEnabled\" (valueChange)=\"toggleAutoRefresh()\"></spa-check>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-internal component -->\r\n <spa-tabs-internal\r\n *ngIf=\"tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-internal>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: CheckComponent, selector: "spa-check", inputs: ["readonly", "display", "value", "infoMessage"], outputs: ["valueChange", "click", "check", "uncheck", "infoClick"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: TabsInternalComponent, selector: "spa-tabs-internal", inputs: ["tableConfigs", "reload", "parentDetails"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
13986
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: DetailsDialog, isStandalone: false, selector: "spa-detailsDialog", outputs: { inputChange: "inputChange" }, ngImport: i0, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-internal component -->\r\n <spa-tabs-internal\r\n *ngIf=\"tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-internal>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: i1.MatDialogActions, selector: "[mat-dialog-actions], mat-dialog-actions, [matDialogActions]", inputs: ["align"] }, { kind: "directive", type: i1.MatDialogContent, selector: "[mat-dialog-content], mat-dialog-content, [matDialogContent]" }, { kind: "directive", type: i8.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: i14$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: SelectComponent, selector: "spa-select", inputs: ["detailsConfig"] }, { kind: "component", type: StepsComponent, selector: "spa-steps", inputs: ["value", "config", "data"] }, { kind: "component", type: FormComponent, selector: "spa-form", inputs: ["files", "data", "config"], outputs: ["buttonClick", "inputChange"] }, { kind: "component", type: AlertComponent, selector: "spa-alert", inputs: ["config", "data"] }, { kind: "component", type: TabsInternalComponent, selector: "spa-tabs-internal", inputs: ["tableConfigs", "reload", "parentDetails"], outputs: ["formRefresh"] }, { kind: "component", type: StatusesComponent, selector: "spa-statuses", inputs: ["config", "data"] }, { kind: "pipe", type: i2.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
13888
13987
|
}
|
|
13889
13988
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: DetailsDialog, decorators: [{
|
|
13890
13989
|
type: Component,
|
|
13891
|
-
args: [{ selector: 'spa-detailsDialog', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <
|
|
13990
|
+
args: [{ selector: 'spa-detailsDialog', standalone: false, template: "<div class=\"dialog-container\">\r\n\r\n <div class=\"dialog-content\">\r\n\r\n <mat-progress-bar mode=\"indeterminate\" *ngIf=\"isProcessing && dataService.appConfig.progressLine\"></mat-progress-bar>\r\n\r\n <div class=\"d-flex justify-content-between align-items-center mt-2\" style=\"padding-left: 24px; padding-right: 24px;\">\r\n\r\n <div>\r\n <label style=\"font-size: 20px; font-weight:500;margin-top: 10px;margin-bottom: 5px;\" >{{titleAction | titlecase}} {{formConfig?.title}}</label>\r\n </div>\r\n\r\n <div class=\"d-flex align-items-center\" style=\"gap: 8px;\">\r\n\r\n <!-- Changed: Auto Refresh icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.autoRefreshConfig\" mat-icon-button\r\n [matTooltip]=\"autoRefreshEnabled ? 'Click to disable auto refresh' : 'Click to enable auto refresh'\" matTooltipPosition=\"above\"\r\n [style.color]=\"autoRefreshEnabled ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"autoRefreshEnabled = !autoRefreshEnabled; toggleAutoRefresh()\">\r\n <mat-icon>autorenew</mat-icon>\r\n </button>\r\n <!-- Changed: Keep Open icon button \u2014 grey when off, green when on, with dynamic tooltip -->\r\n <button *ngIf=\"detailsConfig.allowUserKeepOpen\" mat-icon-button\r\n [matTooltip]=\"userKeepOpen ? 'Click to disable keep open' : 'Click to enable keep open'\" matTooltipPosition=\"above\"\r\n [style.color]=\"userKeepOpen ? '#4caf50' : '#9e9e9e'\"\r\n (click)=\"userKeepOpen = !userKeepOpen\">\r\n <mat-icon>push_pin</mat-icon>\r\n </button>\r\n\r\n <div *ngIf=\"formConfig.mode=='view' && editButton && testVisible(details,editButton.name)\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Edit\" color=\"primary\" (click)=\"setMode('edit')\" [disabled]=\"testDisabled(details,editButton.name)\"><mat-icon>edit</mat-icon></button>\r\n </div>\r\n\r\n <button [disabled]=\"isProcessing\" *ngIf=\"loadByAction\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Refresh\" color=\"primary\" (click)=\"loadData(formConfig.loadAction, detailsConfig.causeTableRefresh)\"><mat-icon class=\"refreshIcon\">cached</mat-icon></button>\r\n \r\n <!-- Added: Top close button when position is 'top' -->\r\n <button *ngIf=\"shouldShowTopClose()\" [disabled]=\"isProcessing\" mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Close\" (click)=\"close()\"><mat-icon>close</mat-icon></button>\r\n\r\n </div>\r\n\r\n </div>\r\n\r\n <div style=\"padding-left: 24px; padding-right: 24px;\">\r\n <spa-steps *ngIf=\"stepConfig && details && stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n </div>\r\n\r\n <mat-dialog-content class=\"mat-typography dialog-scroll-content\">\r\n\r\n <spa-steps *ngIf=\"stepConfig && details && !stepConfig.sticky\" [config]=\"stepConfig\" [data]=\"details\"></spa-steps>\r\n <spa-statuses *ngIf=\"statusConfig && details && !statusConfig?.sticky\" [config]=\"statusConfig\" [data]=\"details\"></spa-statuses>\r\n <spa-alert *ngIf=\"formConfig.alertConfig && !formConfig.alertConfig?.sticky\" [config]=\"formConfig.alertConfig\" [data]=\"details\"></spa-alert>\r\n\r\n <div class=\"tin-input\" style=\"font-size:14px\">\r\n\r\n <p *ngIf=\"formConfig && !details\"><em>Loading...</em></p>\r\n\r\n <spa-form *ngIf=\"formConfig && details\" [files]=\"files\" [data]=\"details\" [config]=\"formConfig\" (inputChange)=\"inputChanged($event)\">\r\n <ng-template #dynamicSelect let-field=\"field\" let-data=\"data\" let-testReadOnly=\"testReadOnly\" let-testRequired=\"testRequired\" let-selectChanged=\"selectChanged\">\r\n <spa-select\r\n [display]=\"field.alias ?? field.name | camelToWords\"\r\n [width]=\"field.width\"\r\n [nullable]=\"field.nullable\"\r\n [options]=\"field.options\"\r\n [masterOptions]=\"field.masterOptions\"\r\n [masterField]=\"field.masterField\"\r\n [optionDisplay]=\"field.optionDisplay ?? 'name'\"\r\n [optionValue]=\"field.optionValue ?? 'value'\"\r\n [(value)]=\"data[field.name]\"\r\n [defaultFirstValue]=\"field.defaultFirstValue\"\r\n [required]=\"testRequired(field)\"\r\n [readonly]=\"testReadOnly(field)\"\r\n [hint]=\"field.hint\"\r\n [detailsConfig]=\"field.detailsConfig\"\r\n [loadAction]=\"field.loadAction\"\r\n [loadIDField]=\"field.loadIDField\"\r\n [field]=\"field\"\r\n [data]=\"data\"\r\n [infoMessage]=\"field.infoMessage\"\r\n [copyContent]=\"field.copyContent\"\r\n (valueChange)=\"selectChanged(field)\"\r\n ></spa-select>\r\n </ng-template>\r\n </spa-form>\r\n\r\n <!-- Changed: Replace mat-tab-group with spa-tabs-internal component -->\r\n <spa-tabs-internal\r\n *ngIf=\"tableConfigs && !(detailsConfig.hideTablesInCreateMode && formConfig?.mode === 'create')\"\r\n [tableConfigs]=\"tableConfigs\"\r\n [reload]=\"tableReload\"\r\n [parentDetails]=\"details\"\r\n (formRefresh)=\"loadData(formConfig.loadAction, false)\">\r\n </spa-tabs-internal>\r\n\r\n </div>\r\n\r\n </mat-dialog-content>\r\n\r\n\r\n </div>\r\n\r\n <mat-dialog-actions >\r\n\r\n <div>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='create' && createButton\" color=\"primary\"\r\n (click)=\"create()\" cdkFocusInitial>{{createButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <button mat-raised-button [disabled]=\"isProcessing\" *ngIf=\"formConfig.mode=='edit' && editButton\" color=\"primary\"\r\n (click)=\"edit()\" cdkFocusInitial>{{editButton.display ?? 'Submit'}}\r\n </button>\r\n\r\n <ng-container *ngFor=\"let btn of extraButtons\">\r\n <button *ngIf=\"formConfig.mode !== 'create' && testVisible(details,btn.name)\" mat-stroked-button [disabled]=\"isProcessing || testDisabled(details,btn.name)\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\" (click)=\"custom(btn)\" cdkFocusInitial>\r\n <mat-icon *ngIf=\"btn.icon\" [ngStyle]=\"{'color': getButtonColor(btn, details)}\">{{btn.icon.name}}</mat-icon>\r\n {{btn.display ?? btn.name | titlecase}}\r\n </button>\r\n </ng-container>\r\n\r\n <!-- Changed: Bottom close button now uses conditional display and custom text -->\r\n <button *ngIf=\"shouldShowBottomClose()\" mat-stroked-button color=\"primary\" (click)=\"close()\">{{getCloseText()}}</button>\r\n\r\n </div>\r\n\r\n <div class=\"col d-flex justify-content-end\" *ngIf=\"smallScreen\">\r\n <button mat-icon-button matTooltipPosition=\"above\" matTooltip=\"Delete\" [disabled]=\"isProcessing\" style=\"color: red;\" (click)=\"delete()\" *ngIf=\"formConfig.mode!='create' && deleteButton\"><mat-icon>delete</mat-icon></button>\r\n </div>\r\n\r\n\r\n </mat-dialog-actions>\r\n\r\n\r\n</div>\r\n\r\n\r\n\r\n\r\n\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
13892
13991
|
}], ctorParameters: () => [{ type: i1$3.BreakpointObserver }, { type: LoaderService }, { type: DataServiceLib }, { type: MessageService }, { type: i1.MatDialogRef }, { type: DetailsDialogConfig, decorators: [{
|
|
13893
13992
|
type: Inject,
|
|
13894
13993
|
args: [MAT_DIALOG_DATA]
|
|
@@ -13933,7 +14032,7 @@ class TableComponent {
|
|
|
13933
14032
|
// Changed: Real-time SignalR subscription management
|
|
13934
14033
|
this.realTimeSubs = [];
|
|
13935
14034
|
this.realTimeEntityName = '';
|
|
13936
|
-
this.
|
|
14035
|
+
this.isSignalRConnected = false; // Changed: Tracks data hub connection state for indicator
|
|
13937
14036
|
// detect screen size changes
|
|
13938
14037
|
this.breakpointObserver.observe(["(max-width: 600px)"]).subscribe((result) => {
|
|
13939
14038
|
this.smallScreen = result.matches;
|
|
@@ -14201,12 +14300,16 @@ class TableComponent {
|
|
|
14201
14300
|
const buttonName = button.name;
|
|
14202
14301
|
let btn = {
|
|
14203
14302
|
...button,
|
|
14204
|
-
detailsConfig:
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14303
|
+
detailsConfig: {
|
|
14304
|
+
...(button.detailsConfig ?? {
|
|
14305
|
+
formConfig: this.config.formConfig,
|
|
14306
|
+
stepConfig: this.config.stepConfig,
|
|
14307
|
+
buttons: this.config.buttons,
|
|
14308
|
+
heroField: this.config.heroField,
|
|
14309
|
+
heroValue: this.config.heroValue,
|
|
14310
|
+
}),
|
|
14311
|
+
allowUserKeepOpen: this.config.allowUserKeepOpen, // Changed: table config is the single source of truth for this setting
|
|
14312
|
+
keepOpenBehavior: this.config.keepOpenBehavior, // Changed: propagate create behavior preference from table config
|
|
14210
14313
|
}
|
|
14211
14314
|
};
|
|
14212
14315
|
this.dialogService.openConfiguredDetailsDialog(btn, row, DetailsDialog).subscribe(result => {
|
|
@@ -14315,29 +14418,26 @@ class TableComponent {
|
|
|
14315
14418
|
return "mat-elevation-z5";
|
|
14316
14419
|
}
|
|
14317
14420
|
}
|
|
14318
|
-
// Changed:
|
|
14421
|
+
// Changed: Real-time tables rely entirely on SignalR — no fallback full refresh
|
|
14319
14422
|
realTimeRefreshOrFallback() {
|
|
14320
14423
|
if (!this.config.realTime) {
|
|
14321
14424
|
this.refreshClicked();
|
|
14322
|
-
return;
|
|
14323
14425
|
}
|
|
14324
|
-
this.pendingRealTimeUpdate = false;
|
|
14325
|
-
setTimeout(() => {
|
|
14326
|
-
if (!this.pendingRealTimeUpdate)
|
|
14327
|
-
this.refreshClicked(); // Fallback if SignalR event didn't arrive
|
|
14328
|
-
}, 3000);
|
|
14329
14426
|
}
|
|
14330
14427
|
// Changed: Set up SignalR subscriptions for real-time entity change updates
|
|
14331
14428
|
setupRealTimeSubscriptions() {
|
|
14332
14429
|
this.realTimeEntityName = this.config.entityName || this.deriveEntityName();
|
|
14430
|
+
// Changed: Subscribe to data hub connection state for real-time indicator
|
|
14431
|
+
this.realTimeSubs.push(this.signalRService.dataHubConnected$.subscribe(connected => {
|
|
14432
|
+
this.isSignalRConnected = connected;
|
|
14433
|
+
}));
|
|
14333
14434
|
// Changed: Subscribe to entity created — add new row to table
|
|
14334
14435
|
this.realTimeSubs.push(this.signalRService.entityCreated$.subscribe(event => {
|
|
14335
14436
|
if (event.entityName !== this.realTimeEntityName)
|
|
14336
14437
|
return;
|
|
14337
14438
|
if (!this.tableDataSource)
|
|
14338
14439
|
return;
|
|
14339
|
-
this.
|
|
14340
|
-
const data = [...this.tableDataSource.data, event.data];
|
|
14440
|
+
const data = [event.data, ...this.tableDataSource.data]; // Changed: Insert at top so new records are immediately visible
|
|
14341
14441
|
this.tableDataSource.data = data;
|
|
14342
14442
|
this.dataSource = data;
|
|
14343
14443
|
}));
|
|
@@ -14347,7 +14447,6 @@ class TableComponent {
|
|
|
14347
14447
|
return;
|
|
14348
14448
|
if (!this.tableDataSource)
|
|
14349
14449
|
return;
|
|
14350
|
-
this.pendingRealTimeUpdate = true;
|
|
14351
14450
|
const idKey = this.findIdKey(event.data);
|
|
14352
14451
|
if (!idKey)
|
|
14353
14452
|
return;
|
|
@@ -14361,7 +14460,6 @@ class TableComponent {
|
|
|
14361
14460
|
return;
|
|
14362
14461
|
if (!this.tableDataSource)
|
|
14363
14462
|
return;
|
|
14364
|
-
this.pendingRealTimeUpdate = true;
|
|
14365
14463
|
const idKey = this.findIdKey(this.tableDataSource.data[0]);
|
|
14366
14464
|
if (!idKey)
|
|
14367
14465
|
return;
|
|
@@ -14390,11 +14488,11 @@ class TableComponent {
|
|
|
14390
14488
|
this.realTimeSubs.forEach(s => s.unsubscribe());
|
|
14391
14489
|
}
|
|
14392
14490
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableComponent, deps: [{ token: DataServiceLib }, { token: MessageService }, { token: i1$3.BreakpointObserver }, { token: i1.MatDialog }, { token: ButtonService }, { token: DialogService }, { token: TableConfigService }, { token: ConditionService }, { token: AuthService }, { token: SignalRService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
14393
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableComponent, isStandalone: false, selector: "spa-table", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
14491
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: TableComponent, isStandalone: false, selector: "spa-table", inputs: { data: "data", tileData: "tileData", config: "config", reload: "reload", activeTab: "activeTab", inTab: "inTab" }, outputs: { dataLoad: "dataLoad", actionSuccess: "actionSuccess", refreshClick: "refreshClick", searchClick: "searchClick", createClick: "createClick", actionClick: "actionClick", inputChange: "inputChange", actionResponse: "actionResponse" }, viewQueries: [{ propertyName: "tablePaginator", first: true, predicate: ["tablePaginator"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" [isRealTime]=\"config.realTime\" [isConnected]=\"isSignalRConnected\"\r\n (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i12$2.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i12$2.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i12$2.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i12$2.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i12$2.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i12$2.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i12$2.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i12$2.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i12$2.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i12$2.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i13.MatPaginator, selector: "mat-paginator", inputs: ["color", "pageIndex", "length", "pageSize", "pageSizeOptions", "hidePageSize", "showFirstLastButtons", "selectConfig", "disabled"], outputs: ["page"], exportAs: ["matPaginator"] }, { kind: "component", type: SearchComponent, selector: "spa-search", inputs: ["config", "smallScreen", "tableDataSource"], outputs: ["searchClick"] }, { kind: "component", type: TableHeaderComponent, selector: "app-table-header", inputs: ["lastSearch", "config", "hideTitle", "tableDataSource", "tileConfig", "smallScreen", "tileReload", "showFilterButton", "data", "tileData", "isRealTime", "isConnected"], outputs: ["createClick", "customClick", "refreshClick", "tileClick", "tileUnClick"] }, { kind: "component", type: TableRowComponent, selector: "app-table-row", inputs: ["column", "row", "config", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: TableActionComponent, selector: "app-table-action", inputs: ["displayedButtons", "config", "row", "smallScreen"], outputs: ["actionClick"] }, { kind: "component", type: CapsulesComponent, selector: "spa-capsules", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "component", type: CardsComponent, selector: "spa-cards", inputs: ["config", "dataSource", "displayedButtons", "smallScreen"], outputs: ["actionClick", "columnClick", "showBannerEvent"] }, { kind: "component", type: GroupsComponent, selector: "spa-groups", inputs: ["config", "dataSource", "displayedButtons"], outputs: ["actionClick"] }, { kind: "pipe", type: CamelToWordsPipe, name: "camelToWords" }] }); }
|
|
14394
14492
|
}
|
|
14395
14493
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TableComponent, decorators: [{
|
|
14396
14494
|
type: Component,
|
|
14397
|
-
args: [{ selector: 'spa-table', standalone: false, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
14495
|
+
args: [{ selector: 'spa-table', standalone: false, template: "\r\n<ng-container *ngIf=\"hasFormAccess\">\r\n\r\n <!-- Search -->\r\n <spa-search\r\n *ngIf=\"config.searchConfig\" [config]=\"config.searchConfig\" [smallScreen]=\"smallScreen\" [tableDataSource]=\"tableDataSource\" style=\"margin-bottom: 20px;\" (searchClick)=\"searchClicked($event)\">\r\n </spa-search>\r\n\r\n <!-- Header -->\r\n <app-table-header\r\n [config]=\"config\" [data]=\"dataSource\" [tableDataSource]=\"tableDataSource\" [tileConfig]=\"config.tileConfig\" [tileData]=\"tileData\" [tileReload]=\"tileReload\" [lastSearch]=\"lastSearch\" [smallScreen]=\"smallScreen\"\r\n [showFilterButton]=\"showFilterButton\" [isRealTime]=\"config.realTime\" [isConnected]=\"isSignalRConnected\"\r\n (createClick)=\"newModel()\" (customClick)=\"customModel($event,null)\"\r\n (refreshClick)=\"refreshClicked()\" (tileClick)=\"tileClicked($event)\" (tileUnClick)=\"tileUnClicked($event)\">\r\n </app-table-header>\r\n\r\n\r\n <!-- Table -->\r\n <div *ngIf=\"!config.viewType || config?.viewType === 'table'\">\r\n\r\n <p *ngIf=\"!config\"><em>Configure Table</em></p>\r\n <p *ngIf=\"!dataSource\"><em>Loading...</em></p>\r\n\r\n <div *ngIf=\"dataSource && (!smallScreen || (smallScreen && dataSource?.length > 0))\">\r\n\r\n <table mat-table [dataSource]=\"tableDataSource\" [ngClass]=\"elevation\">\r\n\r\n <ng-container *ngFor=\"let column of config.columns\" [matColumnDef]=\"column.name\">\r\n <th mat-header-cell *matHeaderCellDef >{{ column.alias ?? column.name | camelToWords }}</th>\r\n <td mat-cell *matCellDef=\"let row;\" class=\"right-padding\" >\r\n\r\n <!-- Rows -->\r\n <app-table-row [column]=\"column\" [row]=\"row\" [config]=\"config\" [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked(column.name, row)\" (columnClick)=\"columnClicked(column, row)\" (showBannerEvent)=\"showBanner($event)\">\r\n </app-table-row>\r\n\r\n </td>\r\n </ng-container>\r\n\r\n <ng-container matColumnDef=\"action\">\r\n <th mat-header-cell *matHeaderCellDef> Action </th>\r\n <td mat-cell *matCellDef=\"let row\" [ngStyle]=\"{width:false ? '20px' : actionsWidth}\">\r\n <div class=\"action-buttons-container\">\r\n\r\n <!-- Actions -->\r\n <app-table-action\r\n [displayedButtons]=\"displayedButtons\" [config]=\"config\" [smallScreen]=\"smallScreen\" [row]=\"row\" (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </app-table-action>\r\n\r\n </div>\r\n </td>\r\n </ng-container>\r\n\r\n\r\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\r\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\" [ngClass]=\"{'make-gray': (config.greyOut && config.greyOut(row)) || row.pendingApproval}\"></tr>\r\n </table>\r\n\r\n </div>\r\n\r\n <!-- Changed: Removed *ngIf condition to keep paginator always in DOM and maintain ViewChild reference -->\r\n <!-- Changed: Added CSS class binding to hide when no data instead of conditional rendering -->\r\n <mat-paginator \r\n #tablePaginator \r\n [pageSizeOptions]=\"config.pageSizes ?? [10, 20, 50]\" \r\n [ngClass]=\"{'paginator-hidden': !dataSource || (smallScreen && dataSource?.length === 0)}\"\r\n showFirstLastButtons>\r\n </mat-paginator>\r\n\r\n </div>\r\n \r\n <!-- Capsules -->\r\n <spa-capsules *ngIf=\"config?.viewType === 'capsule'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\">\r\n </spa-capsules>\r\n\r\n\r\n <!-- Cards -->\r\n <spa-cards *ngIf=\"config?.viewType === 'card'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n [smallScreen]=\"smallScreen\"\r\n (actionClick)=\"actionClicked($event.name, $event.row)\"\r\n (columnClick)=\"columnClicked($event.column, $event.row)\"\r\n (showBannerEvent)=\"showBanner($event)\">\r\n </spa-cards>\r\n\r\n <!-- Groups - Added: New grouped view type -->\r\n <spa-groups *ngIf=\"config?.viewType === 'grouped'\"\r\n [config]=\"config\"\r\n [dataSource]=\"dataSource\"\r\n [displayedButtons]=\"displayedButtons\"\r\n (actionClick)=\"actionClicked($event.name, $event.row, $event.group, $event.button)\">\r\n </spa-groups>\r\n\r\n\r\n <div class=\"tin-center\">\r\n <p *ngIf=\"dataSource?.length == 0\"><em>{{config.noDataMessage ?? 'No Data'}}</em></p>\r\n </div>\r\n\r\n</ng-container>\r\n\r\n\r\n<ng-container *ngIf=\"!hasFormAccess\">\r\n <div class=\"tin-center\">\r\n <p><em>Access Restricted</em></p>\r\n </div>\r\n</ng-container>\r\n\r\n", styles: [".top{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between;align-items:center;margin-bottom:10px;margin-top:10px}.mat-mini-fab{width:32px;height:32px}.mat-mini-fab mat-icon{font-size:16px;margin-top:-3px}.mat-icon-button{width:32px;height:32px}.mat-icon-button mat-icon{font-size:20px;margin-top:-7px}.col-icon{margin-left:10px}.title{margin-top:10px;font-size:larger;font-weight:300}.make-gray{background-color:#f5f5f5}.right-padding{padding-right:10px}.action-buttons-container{display:flex;justify-content:flex-end;align-items:center}.refreshIcon{font-size:22px!important;margin-top:-7px!important}.dialog-container{display:flex;flex-direction:column;height:100%}.dialog-content{flex:1;overflow:hidden;display:flex;flex-direction:column}.dialog-scroll-content{flex:1;overflow-y:auto;max-height:none!important;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input{flex:1;display:flex;flex-direction:column}.dialog-scroll-content>.tin-input>spa-tabs-internal{flex:1;display:flex;flex-direction:column}mat-dialog-actions{flex-shrink:0;justify-content:flex-start}.paginator-hidden{display:none!important}\n"] }]
|
|
14398
14496
|
}], ctorParameters: () => [{ type: DataServiceLib }, { type: MessageService }, { type: i1$3.BreakpointObserver }, { type: i1.MatDialog }, { type: ButtonService }, { type: DialogService }, { type: TableConfigService }, { type: ConditionService }, { type: AuthService }, { type: SignalRService }], propDecorators: { tablePaginator: [{
|
|
14399
14497
|
type: ViewChild,
|
|
14400
14498
|
args: ['tablePaginator', { static: false }]
|
|
@@ -16510,6 +16608,108 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
16510
16608
|
type: Input
|
|
16511
16609
|
}] } });
|
|
16512
16610
|
|
|
16611
|
+
class SpaLandingComponent {
|
|
16612
|
+
constructor(router, authService, el, sanitizer // Changed: inject sanitizer for safe SVG rendering
|
|
16613
|
+
) {
|
|
16614
|
+
this.router = router;
|
|
16615
|
+
this.authService = authService;
|
|
16616
|
+
this.el = el;
|
|
16617
|
+
this.sanitizer = sanitizer;
|
|
16618
|
+
this.mobileMenuOpen = false;
|
|
16619
|
+
this.navScrolled = false;
|
|
16620
|
+
this.currentYear = new Date().getFullYear(); // Used in footer copyright
|
|
16621
|
+
this.observer = null;
|
|
16622
|
+
}
|
|
16623
|
+
// Sanitizes the navBrand SVG string so Angular allows [innerHTML] binding (developer config, not user input)
|
|
16624
|
+
// Changed: inject width/height before sanitising — CSS ::ng-deep cannot reach innerHTML content in library builds
|
|
16625
|
+
get safeNavBrandSvg() {
|
|
16626
|
+
if (!this.config.navBrand?.svgMarkup)
|
|
16627
|
+
return null;
|
|
16628
|
+
const sized = this.config.navBrand.svgMarkup.replace(/^<svg/, '<svg width="36" height="36"');
|
|
16629
|
+
return this.sanitizer.bypassSecurityTrustHtml(sized);
|
|
16630
|
+
}
|
|
16631
|
+
// Sanitizes the hero watermark SVG string
|
|
16632
|
+
get safeHeroWatermarkSvg() {
|
|
16633
|
+
return this.config.hero.watermarkSvg
|
|
16634
|
+
? this.sanitizer.bypassSecurityTrustHtml(this.config.hero.watermarkSvg)
|
|
16635
|
+
: null;
|
|
16636
|
+
}
|
|
16637
|
+
// Sanitizes the footer brand SVG string
|
|
16638
|
+
// Changed: inject width/height before sanitising — same encapsulation fix as navBrand
|
|
16639
|
+
get safeFooterBrandSvg() {
|
|
16640
|
+
if (!this.config.footerBrand?.svgMarkup)
|
|
16641
|
+
return null;
|
|
16642
|
+
const sized = this.config.footerBrand.svgMarkup.replace(/^<svg/, '<svg width="36" height="36"');
|
|
16643
|
+
return this.sanitizer.bypassSecurityTrustHtml(sized);
|
|
16644
|
+
}
|
|
16645
|
+
ngOnInit() {
|
|
16646
|
+
// Apply brand colors as CSS variables on the host element so all child rules inherit them
|
|
16647
|
+
this.applyColors();
|
|
16648
|
+
// Restore session if possible and redirect to home
|
|
16649
|
+
this.authService.tryRestoreSession().then(success => {
|
|
16650
|
+
if (success)
|
|
16651
|
+
this.router.navigate(['home']);
|
|
16652
|
+
});
|
|
16653
|
+
}
|
|
16654
|
+
ngAfterViewInit() {
|
|
16655
|
+
// Scroll-triggered fade-in for sections with lp-animate class
|
|
16656
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
16657
|
+
entries.forEach(entry => {
|
|
16658
|
+
if (entry.isIntersecting)
|
|
16659
|
+
entry.target.classList.add('lp-visible');
|
|
16660
|
+
});
|
|
16661
|
+
}, { threshold: 0.1 });
|
|
16662
|
+
// Observe only within this component's DOM to avoid affecting other pages
|
|
16663
|
+
this.el.nativeElement.querySelectorAll('.lp-animate').forEach((el) => {
|
|
16664
|
+
this.observer?.observe(el);
|
|
16665
|
+
});
|
|
16666
|
+
}
|
|
16667
|
+
ngOnDestroy() {
|
|
16668
|
+
this.observer?.disconnect();
|
|
16669
|
+
}
|
|
16670
|
+
onWindowScroll() {
|
|
16671
|
+
// Add frosted-glass background to navbar after scrolling past hero
|
|
16672
|
+
this.navScrolled = window.scrollY > 50;
|
|
16673
|
+
}
|
|
16674
|
+
scrollToSection(target) {
|
|
16675
|
+
this.mobileMenuOpen = false; // Close mobile menu on nav click
|
|
16676
|
+
const element = document.getElementById(target);
|
|
16677
|
+
element?.scrollIntoView({ behavior: 'smooth' });
|
|
16678
|
+
}
|
|
16679
|
+
toggleMobileMenu() {
|
|
16680
|
+
this.mobileMenuOpen = !this.mobileMenuOpen;
|
|
16681
|
+
}
|
|
16682
|
+
navigateTo(path) {
|
|
16683
|
+
this.router.navigate([path]);
|
|
16684
|
+
this.mobileMenuOpen = false;
|
|
16685
|
+
}
|
|
16686
|
+
// Returns light tinted background for feature icon (hex + '20' = ~12% opacity)
|
|
16687
|
+
iconBg(hex) {
|
|
16688
|
+
return hex + '20';
|
|
16689
|
+
}
|
|
16690
|
+
// Sets CSS custom properties on host element so child rules use app-specific brand colors
|
|
16691
|
+
applyColors() {
|
|
16692
|
+
const s = this.el.nativeElement.style;
|
|
16693
|
+
const c = this.config.colors;
|
|
16694
|
+
s.setProperty('--lp-primary', c.primary);
|
|
16695
|
+
s.setProperty('--lp-primary-dark', c.primaryDark);
|
|
16696
|
+
s.setProperty('--lp-primary-light', c.primaryLight);
|
|
16697
|
+
s.setProperty('--lp-secondary', c.secondary);
|
|
16698
|
+
s.setProperty('--lp-secondary-light', c.secondaryLight);
|
|
16699
|
+
}
|
|
16700
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaLandingComponent, deps: [{ token: i1$2.Router }, { token: AuthService }, { token: i0.ElementRef }, { token: i1$5.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
16701
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.14", type: SpaLandingComponent, isStandalone: false, selector: "spa-landing", inputs: { config: "config" }, host: { listeners: { "window:scroll": "onWindowScroll()" } }, ngImport: i0, template: "<!-- ===== NAVBAR ===== -->\n<nav class=\"lp-navbar\" [class.lp-navbar-scrolled]=\"navScrolled\">\n <div class=\"lp-container lp-nav-inner\">\n <!-- Brand: custom SVG override OR default img+name -->\n <div class=\"lp-nav-brand\" (click)=\"scrollToSection('lp-hero')\">\n <ng-container *ngIf=\"config.navBrand; else defaultNavBrand\">\n <span class=\"lp-nav-brand-svg\" [innerHTML]=\"safeNavBrandSvg\"></span>\n <div *ngIf=\"config.navBrand.wordmarkLine1\" class=\"lp-nav-brand-wordmark\">\n <span class=\"lp-nav-wordmark-line1\">{{ config.navBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.navBrand.wordmarkLine2\" class=\"lp-nav-wordmark-line2\">{{ config.navBrand.wordmarkLine2 }}</span>\n </div>\n </ng-container>\n <ng-template #defaultNavBrand>\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-nav-logo\">\n <span class=\"lp-nav-brand-text\">{{ config.appName }}</span>\n </ng-template>\n </div>\n <!-- Desktop nav links -->\n <div class=\"lp-nav-links\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n </div>\n <!-- Mobile menu overlay -->\n <div class=\"lp-mobile-menu\" [class.lp-mobile-menu-open]=\"mobileMenuOpen\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n <div class=\"lp-mobile-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n </div>\n <!-- Desktop auth -->\n <div class=\"lp-nav-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary lp-btn-sm\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n <!-- Hamburger for mobile -->\n <button class=\"lp-hamburger\" (click)=\"toggleMobileMenu()\" [class.lp-hamburger-open]=\"mobileMenuOpen\">\n <span></span><span></span><span></span>\n </button>\n </div>\n</nav>\n\n<!-- ===== HERO ===== -->\n<section id=\"lp-hero\" class=\"lp-hero-bg\" [class.lp-hero-split]=\"config.hero.layout === 'split'\">\n <!-- Optional faded watermark SVG (e.g. company logo) -->\n <!-- Changed: wrapper div gets Angular scoping attribute; inner div holds innerHTML so svg size CSS applies to scoped .lp-hero-watermark-inner -->\n <div *ngIf=\"config.hero.watermarkSvg\" class=\"lp-hero-watermark\" aria-hidden=\"true\">\n <div class=\"lp-hero-watermark-inner\" [innerHTML]=\"safeHeroWatermarkSvg\"></div>\n </div>\n\n <div class=\"lp-container lp-hero-content\">\n <!-- Left / centered column: brand, headline, CTAs -->\n <div class=\"lp-hero-main\">\n <!-- App brand: logo + name + decorative divider -->\n <div class=\"lp-hero-brand\">\n <div class=\"lp-hero-brand-logo\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-hero-brand-icon\">\n <span class=\"lp-hero-brand-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-hero-divider\" aria-hidden=\"true\">\n <span class=\"lp-hero-divider-line\"></span>\n <span class=\"lp-hero-divider-dot\"></span>\n <span class=\"lp-hero-divider-line\"></span>\n </div>\n </div>\n <!-- Hero text: headline, subtitle, CTAs, trust signals -->\n <div class=\"lp-hero-text\">\n <div *ngIf=\"config.hero.badge\" class=\"lp-hero-badge\">{{ config.hero.badge }}</div>\n <h1>{{ config.hero.headline }} <span class=\"lp-gradient-text\">{{ config.hero.gradientText }}</span></h1>\n <p class=\"lp-hero-subtitle\">{{ config.hero.subtitle }}</p>\n <div class=\"lp-hero-ctas\">\n <button class=\"lp-btn lp-btn-primary lp-btn-lg\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">\n {{ config.hero.primaryCTA }}\n </button>\n <button class=\"lp-btn lp-btn-outline lp-btn-lg\" (click)=\"scrollToSection(config.hero.secondaryCTASection)\">\n {{ config.hero.secondaryCTA }}\n </button>\n </div>\n <!-- Trust signals with checkmark SVG icons -->\n <div class=\"lp-trust-signals\">\n <div *ngFor=\"let signal of config.hero.trustSignals\" class=\"lp-trust-item\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M20 6L9 17l-5-5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n <span>{{ signal }}</span>\n </div>\n </div>\n </div>\n </div>\n <!-- App visual: always rendered when projected content exists (stacked below hero text) -->\n <!-- Changed: removed *ngIf so visual always renders below text; layout is always stacked/column -->\n <div class=\"lp-hero-visual\">\n <ng-content select=\"[lpHeroVisual]\"></ng-content>\n </div>\n </div>\n</section>\n\n<!-- ===== METRICS (optional) ===== -->\n<section *ngIf=\"config.metrics\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-metrics-grid\">\n <div *ngFor=\"let metric of config.metrics\" class=\"lp-metric-card\">\n <div class=\"lp-metric-value\">{{ metric.value }}</div>\n <div class=\"lp-metric-label\">{{ metric.label }}</div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FEATURES ===== -->\n<section id=\"lp-features\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.features.title }}</h2>\n <p>{{ config.features.subtitle }}</p>\n </div>\n <div class=\"lp-features-grid\">\n <div *ngFor=\"let feature of config.features.items\" class=\"lp-feature-card\">\n <!-- Icon tint: hex + '20' = ~12% opacity background -->\n <div class=\"lp-feature-icon\"\n [style.background]=\"iconBg(feature.color)\"\n [style.color]=\"feature.color\">\n <span class=\"material-icons\">{{ feature.icon }}</span>\n </div>\n <h3 class=\"lp-feature-title\">{{ feature.title }}</h3>\n <p class=\"lp-feature-desc\">{{ feature.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== WORKFLOW (optional) ===== -->\n<section *ngIf=\"config.workflow\" id=\"lp-workflow\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.workflow!.title }}</h2>\n <p *ngIf=\"config.workflow!.subtitle\">{{ config.workflow!.subtitle }}</p>\n </div>\n\n <!-- Road metaphor layout: horizontal road line with waypoints -->\n <div *ngIf=\"config.workflow!.style === 'road'\" class=\"lp-workflow-road\">\n <div class=\"lp-road-line\"></div>\n <div *ngFor=\"let step of config.workflow!.steps\" class=\"lp-waypoint\">\n <div class=\"lp-waypoint-marker\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <div class=\"lp-waypoint-number\">Step {{ step.number }}</div>\n <div class=\"lp-waypoint-title\">{{ step.title }}</div>\n <div class=\"lp-waypoint-desc\">{{ step.description }}</div>\n </div>\n </div>\n\n <!-- Default steps grid layout -->\n <div *ngIf=\"!config.workflow!.style || config.workflow!.style === 'steps'\" class=\"lp-steps-grid\">\n <div *ngFor=\"let step of config.workflow!.steps; let i = index\" class=\"lp-step-card\">\n <div class=\"lp-step-number\">{{ step.number }}</div>\n <div class=\"lp-step-icon\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <h3 class=\"lp-step-title\">{{ step.title }}</h3>\n <p class=\"lp-step-desc\">{{ step.description }}</p>\n <!-- Connector line between steps, not after last -->\n <div *ngIf=\"i < config.workflow!.steps.length - 1\" class=\"lp-step-connector\"></div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== CUSTOM SECTION SLOT ===== -->\n<!-- Consumer projects app-specific sections here (fleet board, benefits rows, etc.)\n using [lpCustomSection] attribute on a wrapper element -->\n<ng-content select=\"[lpCustomSection]\"></ng-content>\n\n<!-- ===== MODULES (optional) ===== -->\n<section *ngIf=\"config.modules\" id=\"lp-modules\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.modules!.title }}</h2>\n <p>{{ config.modules!.subtitle }}</p>\n </div>\n <div class=\"lp-modules-grid\">\n <div *ngFor=\"let group of config.modules!.groups\" class=\"lp-module-group\">\n <div class=\"lp-module-group-label\">{{ group.category }}</div>\n <div class=\"lp-module-tiles\">\n <div *ngFor=\"let tile of group.tiles\" class=\"lp-module-tile\">\n <span class=\"material-icons\">{{ tile.icon }}</span>\n <span>{{ tile.name }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== SECURITY (optional \u2014 dark background) ===== -->\n<section *ngIf=\"config.security\" class=\"lp-section lp-section-dark lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header lp-header-light\">\n <h2>{{ config.security!.title }}</h2>\n <p>{{ config.security!.subtitle }}</p>\n </div>\n <div class=\"lp-security-grid\">\n <div *ngFor=\"let item of config.security!.items\" class=\"lp-security-card\">\n <div class=\"lp-security-icon\">\n <span class=\"material-icons\">{{ item.icon }}</span>\n </div>\n <h3>{{ item.title }}</h3>\n <p>{{ item.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== TESTIMONIALS (optional) ===== -->\n<section *ngIf=\"config.testimonials\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.testimonials!.title }}</h2>\n <p *ngIf=\"config.testimonials!.subtitle\">{{ config.testimonials!.subtitle }}</p>\n </div>\n <div class=\"lp-testimonials-grid\">\n <div *ngFor=\"let item of config.testimonials!.items\" class=\"lp-testimonial-card\">\n <div class=\"lp-stars\">\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n </div>\n <p class=\"lp-testimonial-quote\">\"{{ item.quote }}\"</p>\n <div class=\"lp-testimonial-author\">\n <div class=\"lp-author-avatar\">{{ item.authorInitials }}</div>\n <div>\n <div class=\"lp-author-name\">{{ item.authorName }}</div>\n <div class=\"lp-author-role\">{{ item.authorRole }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== PRICING (optional) ===== -->\n<section *ngIf=\"config.pricing\" id=\"lp-pricing\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.pricing!.title }}</h2>\n <p *ngIf=\"config.pricing!.subtitle\">{{ config.pricing!.subtitle }}</p>\n </div>\n <div class=\"lp-pricing-grid\">\n <div *ngFor=\"let plan of config.pricing!.plans\"\n class=\"lp-pricing-card\"\n [class.lp-pricing-popular]=\"plan.popular\">\n <div *ngIf=\"plan.popular\" class=\"lp-popular-badge\">Most Popular</div>\n <div class=\"lp-pricing-header\">\n <h3>{{ plan.name }}</h3>\n <div class=\"lp-pricing-price\">\n <span class=\"lp-price-amount\">{{ plan.price }}</span>\n <span *ngIf=\"plan.period\" class=\"lp-price-period\">{{ plan.period }}</span>\n </div>\n <p class=\"lp-pricing-desc\">{{ plan.description }}</p>\n </div>\n <ul class=\"lp-pricing-features\">\n <li *ngFor=\"let feature of plan.features\">{{ feature }}</li>\n </ul>\n <!-- Popular plans get primary button, others get outline -->\n <button *ngIf=\"plan.popular\"\n class=\"lp-btn lp-btn-primary lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n <button *ngIf=\"!plan.popular\"\n class=\"lp-btn lp-btn-outline lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FINAL CTA BANNER (optional) ===== -->\n<section *ngIf=\"config.finalCta\" class=\"lp-final-cta lp-animate\">\n <div class=\"lp-container lp-cta-content\">\n <h2>{{ config.finalCta!.headline }}</h2>\n <p>{{ config.finalCta!.subtitle }}</p>\n <div class=\"lp-cta-buttons\">\n <button class=\"lp-btn lp-btn-white lp-btn-lg\"\n (click)=\"navigateTo(config.finalCta!.primaryCTARoute || 'signup')\">\n {{ config.finalCta!.primaryCTA }}\n </button>\n <button *ngIf=\"config.finalCta!.secondaryCTA\"\n class=\"lp-btn lp-btn-outline-white lp-btn-lg\"\n (click)=\"scrollToSection(config.finalCta!.secondaryCTASection || '')\">\n {{ config.finalCta!.secondaryCTA }}\n </button>\n </div>\n </div>\n</section>\n\n<!-- ===== FOOTER ===== -->\n<footer class=\"lp-footer\" id=\"lp-footer\">\n <div class=\"lp-container\">\n\n <!-- Rich multi-column footer when footerColumns provided -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length; else simpleFooter\" class=\"lp-footer-grid\">\n <!-- Brand column -->\n <div class=\"lp-footer-brand-col\">\n <!-- Custom SVG footer brand OR default img+name -->\n <ng-container *ngIf=\"config.footerBrand; else defaultFooterBrand\">\n <div class=\"lp-footer-brand-custom\">\n <span class=\"lp-footer-brand-svg\" [innerHTML]=\"safeFooterBrandSvg\"></span>\n <div *ngIf=\"config.footerBrand.wordmarkLine1\" class=\"lp-footer-brand-wordmark\">\n <span class=\"lp-footer-wordmark-line1\">{{ config.footerBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.footerBrand.wordmarkLine2\" class=\"lp-footer-wordmark-line2\">{{ config.footerBrand.wordmarkLine2 }}</span>\n </div>\n </div>\n </ng-container>\n <ng-template #defaultFooterBrand>\n <div class=\"lp-footer-brand-default\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n </ng-template>\n <p *ngIf=\"config.footerTagline\" class=\"lp-footer-tagline\">{{ config.footerTagline }}</p>\n </div>\n <!-- Link columns -->\n <div *ngFor=\"let col of config.footerColumns\" class=\"lp-footer-col\">\n <h4>{{ col.title }}</h4>\n <a *ngFor=\"let link of col.links\"\n (click)=\"link.section ? scrollToSection(link.section) : (link.route ? navigateTo(link.route) : null)\">\n {{ link.label }}\n </a>\n </div>\n </div>\n\n <!-- Simple single-row footer (default) -->\n <ng-template #simpleFooter>\n <div class=\"lp-footer-inner\">\n <div class=\"lp-footer-brand\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-footer-links\">\n <a (click)=\"navigateTo('login')\">Log in</a>\n <a (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Sign up</a>\n </div>\n <div class=\"lp-footer-copy\">\n © {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.\n </div>\n </div>\n </ng-template>\n\n <!-- Footer bottom bar (shown for both layouts) -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length\" class=\"lp-footer-bottom\">\n <span>© {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.</span>\n </div>\n </div>\n</footer>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\";:host{--lp-primary: #1E3A5F;--lp-primary-dark: #152C4A;--lp-primary-light: #EBF0F7;--lp-secondary: #F59E0B;--lp-secondary-light:#FEF3C7;--lp-dark: #0D1117;--lp-gray-800: #1F2937;--lp-gray-700: #374151;--lp-gray-600: #4B5563;--lp-gray-400: #9CA3AF;--lp-gray-200: #E5E7EB;--lp-gray-100: #F3F4F6;--lp-gray-50: #F9FAFB;--lp-white: #FFFFFF;--lp-radius: 12px;--lp-radius-sm: 8px;--lp-radius-lg: 20px;--lp-radius-xl: 28px;--lp-shadow-xs: 0 1px 2px rgba(0,0,0,.05);--lp-shadow: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05);--lp-shadow-md: 0 4px 8px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.04);--lp-shadow-lg: 0 12px 28px rgba(0,0,0,.1), 0 4px 8px rgba(0,0,0,.06);--lp-shadow-xl: 0 20px 40px rgba(0,0,0,.12), 0 8px 16px rgba(0,0,0,.07);--lp-ease: cubic-bezier(.4, 0, .2, 1);--lp-spring: cubic-bezier(.34, 1.56, .64, 1);display:block;font-family:Outfit,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--lp-gray-700);line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased}.lp-container{max-width:1200px;margin:0 auto;padding:0 32px}.lp-navbar{position:fixed;top:0;left:0;right:0;z-index:1000;padding:18px 0;transition:background .3s var(--lp-ease),padding .3s var(--lp-ease),box-shadow .3s var(--lp-ease)}.lp-navbar-scrolled{background:#ffffffeb;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);box-shadow:0 1px #0000000f,0 2px 8px #0000000a;padding:12px 0}.lp-nav-inner{display:flex;align-items:center;justify-content:space-between}.lp-nav-brand{display:flex;align-items:center;gap:10px;cursor:pointer;text-decoration:none}.lp-nav-logo{width:30px;height:30px;border-radius:7px;object-fit:contain}.lp-nav-brand-text{font-size:18px;font-weight:700;color:var(--lp-primary);letter-spacing:-.01em}.lp-nav-links{display:flex;align-items:center;gap:36px}.lp-nav-links a{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none;letter-spacing:.01em}.lp-nav-links a:hover{color:var(--lp-primary)}.lp-nav-auth{display:flex;align-items:center;gap:12px}.lp-nav-login{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none}.lp-nav-login:hover{color:var(--lp-primary)}.lp-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:4px;z-index:1001}.lp-hamburger span{display:block;width:22px;height:2px;background:var(--lp-gray-700);border-radius:2px;transition:transform .3s var(--lp-ease),opacity .3s}.lp-hamburger-open span:nth-child(1){transform:translateY(7px) rotate(45deg)}.lp-hamburger-open span:nth-child(2){opacity:0}.lp-hamburger-open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.lp-mobile-menu{display:none}.lp-mobile-auth{display:flex;flex-direction:column;gap:12px;margin-top:24px;padding-top:24px;border-top:1px solid var(--lp-gray-200)}.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:Outfit,sans-serif;font-size:14px;font-weight:600;padding:10px 22px;border-radius:var(--lp-radius-sm);border:none;cursor:pointer;transition:all .25s var(--lp-ease);text-decoration:none;white-space:nowrap;letter-spacing:.01em}.lp-btn-primary{background:var(--lp-primary);color:var(--lp-white);box-shadow:0 1px 3px #00000026,inset 0 1px #ffffff1f}.lp-btn-primary:hover{background:var(--lp-primary-dark);transform:translateY(-1px);box-shadow:0 6px 16px #0003,inset 0 1px #ffffff1f}.lp-btn-primary:active{transform:translateY(0)}.lp-btn-outline{background:transparent;color:var(--lp-primary);border:1.5px solid var(--lp-primary)}.lp-btn-outline:hover{background:var(--lp-primary);color:var(--lp-white);transform:translateY(-1px);box-shadow:0 4px 12px #00000026}.lp-btn-sm{padding:7px 16px;font-size:13px}.lp-btn-lg{padding:14px 32px;font-size:15px;border-radius:var(--lp-radius)}.lp-btn-block{width:100%}.lp-hero-bg{position:relative;overflow:hidden;padding:140px 0 100px;background:radial-gradient(ellipse 80% 60% at 60% -10%,color-mix(in srgb,var(--lp-primary) 8%,transparent) 0%,transparent 70%),radial-gradient(ellipse 60% 50% at -10% 80%,color-mix(in srgb,var(--lp-secondary) 6%,transparent) 0%,transparent 70%),linear-gradient(160deg,var(--lp-primary-light) 0%,#FAFBFF 45%,var(--lp-secondary-light) 100%);min-height:100vh;display:flex;align-items:center}.lp-hero-bg:before{content:\"\";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(0,0,0,.04) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%);mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%)}.lp-hero-bg:after{content:\"\";position:absolute;width:700px;height:700px;border-radius:50%;background:color-mix(in srgb,var(--lp-primary) 5%,transparent);top:-200px;right:-150px;filter:blur(100px);pointer-events:none}.lp-hero-content{position:relative;z-index:1;text-align:center;width:100%}.lp-hero-badge{display:inline-flex;align-items:center;gap:6px;background:color-mix(in srgb,var(--lp-primary) 10%,transparent);color:var(--lp-primary);border:1px solid color-mix(in srgb,var(--lp-primary) 20%,transparent);border-radius:100px;padding:6px 16px;font-size:13px;font-weight:600;margin-bottom:20px;letter-spacing:.02em}.lp-hero-brand{margin-bottom:20px;text-align:center}.lp-hero-brand-logo{display:inline-flex;align-items:center;gap:14px;margin-bottom:12px}.lp-hero-brand-icon{height:52px;width:52px;border-radius:14px;object-fit:contain;box-shadow:0 4px 12px #0000001f}.lp-hero-brand-name{font-size:38px;font-weight:800;color:var(--lp-primary);letter-spacing:-.03em;font-family:Outfit,sans-serif}.lp-hero-divider{display:flex;align-items:center;justify-content:center;gap:10px}.lp-hero-divider-line{display:block;height:1px;width:40px;background:linear-gradient(90deg,transparent,var(--lp-primary),transparent);opacity:.3}.lp-hero-divider-dot{display:block;height:5px;width:5px;border-radius:50%;background:var(--lp-secondary);opacity:.8}.lp-hero-text{max-width:760px;margin:0 auto 64px}.lp-hero-text h1{font-family:Outfit,sans-serif;font-size:68px;font-weight:900;line-height:1.05;color:var(--lp-dark);margin:0 0 24px;letter-spacing:-.03em}.lp-gradient-text{background:linear-gradient(135deg,var(--lp-primary) 0%,var(--lp-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.lp-hero-subtitle{font-size:19px;color:var(--lp-gray-600);line-height:1.65;font-weight:400;max-width:560px;margin:0 auto 36px}.lp-hero-ctas{display:flex;align-items:center;justify-content:center;gap:14px;margin-bottom:32px;flex-wrap:wrap}.lp-trust-signals{display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap}.lp-trust-item{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:500;color:var(--lp-gray-500, #6B7280)}.lp-trust-item svg{color:var(--lp-primary);flex-shrink:0}.lp-section{padding:100px 0;background:var(--lp-white)}.lp-section-alt{background:var(--lp-gray-50)}.lp-section-header{text-align:center;margin-bottom:64px}.lp-section-header h2{font-family:Outfit,sans-serif;font-size:42px;font-weight:800;color:var(--lp-dark);margin:0 0 14px;letter-spacing:-.025em;line-height:1.15}.lp-section-header p{font-size:17px;color:var(--lp-gray-600);max-width:540px;margin:0 auto;line-height:1.65}.lp-features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.lp-feature-card{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:36px 28px;transition:transform .25s var(--lp-ease),box-shadow .25s var(--lp-ease);border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);position:relative;overflow:hidden}.lp-feature-card:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--lp-primary),var(--lp-secondary));transform:scaleX(0);transform-origin:left;transition:transform .3s var(--lp-ease);border-radius:3px 3px 0 0}.lp-feature-card:hover{transform:translateY(-5px);box-shadow:var(--lp-shadow-xl);border-color:transparent}.lp-feature-card:hover:before{transform:scaleX(1)}.lp-feature-icon{width:52px;height:52px;border-radius:var(--lp-radius-sm);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.lp-feature-icon .material-icons{font-size:26px}.lp-feature-title{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-dark);margin:0 0 10px;letter-spacing:-.01em}.lp-feature-desc{font-size:14px;color:var(--lp-gray-600);line-height:1.65;margin:0}.lp-steps-grid{display:flex;align-items:flex-start;justify-content:space-between;position:relative;gap:16px}.lp-step-card{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;padding:0 8px}.lp-step-number{font-size:11px;font-weight:700;color:var(--lp-secondary);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px}.lp-step-icon{width:56px;height:56px;border-radius:50%;background:var(--lp-primary);display:flex;align-items:center;justify-content:center;margin-bottom:18px;box-shadow:0 4px 14px color-mix(in srgb,var(--lp-primary) 30%,transparent);border:3px solid var(--lp-white);position:relative;z-index:1}.lp-step-icon .material-icons{font-size:24px;color:var(--lp-white)}.lp-step-title{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--lp-dark);margin:0 0 8px;letter-spacing:-.01em}.lp-step-desc{font-size:13px;color:var(--lp-gray-600);line-height:1.55;margin:0}.lp-step-connector{position:absolute;top:40px;left:60%;width:80%;height:2px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 0px,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 10px,transparent 10px,transparent 18px);pointer-events:none;z-index:0}.lp-metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--lp-gray-200);border-radius:var(--lp-radius-lg);overflow:hidden;background:var(--lp-white);box-shadow:var(--lp-shadow)}.lp-metric-card{text-align:center;padding:40px 24px;border-right:1px solid var(--lp-gray-200);transition:background .2s}.lp-metric-card:last-child{border-right:none}.lp-metric-card:hover{background:var(--lp-gray-50)}.lp-metric-value{font-family:Outfit,sans-serif;font-size:48px;font-weight:900;color:var(--lp-primary);line-height:1;letter-spacing:-.03em;margin-bottom:8px}.lp-metric-label{font-size:14px;font-weight:500;color:var(--lp-gray-600)}.lp-modules-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.lp-module-group{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:28px 28px 24px;border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);transition:box-shadow .2s}.lp-module-group:hover{box-shadow:var(--lp-shadow-md)}.lp-module-group-label{font-family:Outfit,sans-serif;font-size:11px;font-weight:700;color:var(--lp-primary);margin-bottom:16px;text-transform:uppercase;letter-spacing:1px}.lp-module-tiles{display:flex;flex-wrap:wrap;gap:8px}.lp-module-tile{display:inline-flex;align-items:center;gap:6px;background:var(--lp-gray-50);border:1px solid var(--lp-gray-200);padding:7px 13px;border-radius:100px;font-size:12px;font-weight:600;color:var(--lp-gray-700);transition:all .2s var(--lp-ease);cursor:default}.lp-module-tile:hover{background:color-mix(in srgb,var(--lp-primary) 6%,transparent);border-color:color-mix(in srgb,var(--lp-primary) 25%,transparent);color:var(--lp-primary);transform:translateY(-1px)}.lp-module-tile .material-icons{font-size:15px;color:var(--lp-primary)}.lp-footer{background:var(--lp-dark);padding:44px 0 36px;border-top:1px solid rgba(255,255,255,.04)}.lp-footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}.lp-footer-brand{display:flex;align-items:center;gap:10px}.lp-footer-logo{width:30px;height:30px;border-radius:7px;object-fit:contain;opacity:.9}.lp-footer-name{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-white);letter-spacing:-.01em}.lp-footer-links{display:flex;gap:28px}.lp-footer-links a{font-size:14px;font-weight:500;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-links a:hover{color:var(--lp-white)}.lp-footer-copy{font-size:13px;color:#ffffff59;width:100%;text-align:center;padding-top:20px;margin-top:4px;border-top:1px solid rgba(255,255,255,.06)}.lp-animate{opacity:0;transform:translateY(28px);transition:opacity .65s var(--lp-ease),transform .65s var(--lp-ease)}.lp-visible{opacity:1;transform:translateY(0)}.lp-visible .lp-feature-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-feature-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-feature-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-feature-card:nth-child(4){transition-delay:.18s}.lp-visible .lp-feature-card:nth-child(5){transition-delay:.24s}.lp-visible .lp-feature-card:nth-child(6){transition-delay:.3s}.lp-visible .lp-step-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-step-card:nth-child(2){transition-delay:80ms}.lp-visible .lp-step-card:nth-child(3){transition-delay:.16s}.lp-visible .lp-step-card:nth-child(4){transition-delay:.24s}.lp-visible .lp-metric-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-metric-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-metric-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-metric-card:nth-child(4){transition-delay:.18s}@media (max-width: 1024px){.lp-hero-text h1{font-size:52px}.lp-section-header h2{font-size:36px}.lp-features-grid,.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card:nth-child(2){border-right:none}.lp-metrics-grid .lp-metric-card:nth-child(3){border-top:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:nth-child(4){border-top:1px solid var(--lp-gray-200)}.lp-steps-grid{flex-wrap:wrap;justify-content:center;gap:40px}.lp-step-connector{display:none}}@media (max-width: 767px){.lp-container{padding:0 20px}.lp-nav-links{display:none}.lp-hamburger{display:flex}.lp-mobile-auth{display:none}.lp-mobile-menu{display:none;position:fixed;inset:0;background:#fffffff7;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;align-items:center;justify-content:center;gap:28px;z-index:999}.lp-mobile-menu-open{display:flex}.lp-mobile-menu a{font-size:22px;font-weight:700;color:var(--lp-dark);cursor:pointer;text-decoration:none;letter-spacing:-.01em}.lp-hero-bg{padding:100px 0 60px;min-height:auto}.lp-hero-text h1{font-size:38px}.lp-hero-brand-name{font-size:28px}.lp-hero-subtitle{font-size:16px}.lp-hero-ctas{flex-direction:column}.lp-hero-ctas .lp-btn{width:100%;justify-content:center}.lp-trust-signals{gap:16px}.lp-section{padding:72px 0}.lp-section-header{margin-bottom:40px}.lp-section-header h2{font-size:28px}.lp-features-grid{grid-template-columns:1fr}.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card{border-right:none;border-bottom:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:last-child{border-bottom:none}.lp-metric-value{font-size:36px}.lp-modules-grid{grid-template-columns:1fr}.lp-steps-grid{flex-direction:column;align-items:center;gap:32px}.lp-step-connector{display:none}.lp-footer-inner{flex-direction:column;align-items:center;text-align:center}}.lp-nav-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-nav-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-nav-brand-wordmark{display:flex;flex-direction:column;line-height:1.1;margin-left:8px}.lp-nav-wordmark-line1{font-size:15px;font-weight:800;color:#1a3a7a;letter-spacing:-.01em;font-family:Outfit,sans-serif}.lp-nav-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-hero-bg{position:relative}.lp-hero-watermark{position:absolute;inset:0;display:flex;align-items:flex-start;justify-content:center;padding-top:40px;pointer-events:none;z-index:1}.lp-hero-watermark-inner{width:700px;height:700px;opacity:.04;overflow:hidden;flex-shrink:0}@media (min-width: 768px){.lp-hero-watermark-inner{width:800px;height:800px}}.lp-hero-content{display:flex;flex-direction:column;align-items:center}.lp-hero-main{z-index:1;width:100%}.lp-hero-visual{position:relative;z-index:1;width:100%;max-width:900px;margin:48px auto 0}.lp-workflow-road{position:relative;display:flex;justify-content:space-between;align-items:flex-start;padding:48px 0 24px;gap:16px}.lp-road-line{position:absolute;top:72px;left:6%;right:6%;height:3px;background:linear-gradient(90deg,var(--lp-primary-light),var(--lp-primary),var(--lp-primary-light));border-radius:2px;opacity:.4}.lp-waypoint{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1}.lp-waypoint-marker{width:52px;height:52px;border-radius:50%;background:var(--lp-primary);color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:12px;box-shadow:0 4px 16px color-mix(in srgb,var(--lp-primary) 35%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}.lp-waypoint:hover .lp-waypoint-marker{transform:translateY(-4px) scale(1.08)}.lp-waypoint-marker .material-icons{font-size:22px}.lp-waypoint-number{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lp-primary);margin-bottom:6px;opacity:.7}.lp-waypoint-title{font-size:14px;font-weight:700;color:var(--lp-dark);margin-bottom:6px;font-family:Outfit,sans-serif}.lp-waypoint-desc{font-size:12px;color:var(--lp-gray-600);line-height:1.5;max-width:140px}.lp-section-dark{background:var(--lp-dark);color:#fff}.lp-section-dark.lp-section{padding:96px 0}.lp-header-light h2{color:#fff}.lp-header-light p{color:#ffffffa6}.lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}.lp-security-card{background:#ffffff0d;border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:32px 28px;transition:background .25s ease}.lp-security-card:hover{background:#ffffff14}.lp-security-icon{width:52px;height:52px;border-radius:14px;background:#ffffff1a;display:flex;align-items:center;justify-content:center;margin-bottom:20px;color:#fff}.lp-security-icon .material-icons{font-size:24px}.lp-security-card h3{font-size:18px;font-weight:700;color:#fff;margin:0 0 12px;font-family:Outfit,sans-serif}.lp-security-card p{font-size:14px;color:#fff9;line-height:1.65;margin:0}.lp-testimonials-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px}.lp-testimonial-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:32px 28px;display:flex;flex-direction:column;gap:16px;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-testimonial-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-2px)}.lp-stars{display:flex;gap:2px;color:var(--lp-secondary)}.lp-stars .material-icons{font-size:18px}.lp-testimonial-quote{font-size:15px;color:var(--lp-gray-700);line-height:1.65;font-style:italic;margin:0;flex:1}.lp-testimonial-author{display:flex;align-items:center;gap:12px}.lp-author-avatar{width:44px;height:44px;border-radius:50%;background:var(--lp-primary);color:#fff;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:Outfit,sans-serif}.lp-author-name{font-size:14px;font-weight:700;color:var(--lp-dark)}.lp-author-role{font-size:12px;color:var(--lp-gray-400)}.lp-pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;align-items:start}.lp-pricing-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:36px 28px 28px;position:relative;display:flex;flex-direction:column;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-pricing-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-3px)}.lp-pricing-popular{border-color:var(--lp-primary);box-shadow:0 0 0 2px var(--lp-primary);transform:scale(1.03)}.lp-pricing-popular:hover{transform:scale(1.03) translateY(-3px)}.lp-popular-badge{position:absolute;top:-12px;left:50%;transform:translate(-50%);background:var(--lp-primary);color:#fff;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 14px;border-radius:100px;white-space:nowrap;font-family:Outfit,sans-serif}.lp-pricing-header{margin-bottom:24px}.lp-pricing-header h3{font-size:20px;font-weight:800;color:var(--lp-dark);margin:0 0 12px;font-family:Outfit,sans-serif}.lp-pricing-price{display:flex;align-items:baseline;gap:4px;margin-bottom:8px}.lp-price-amount{font-size:40px;font-weight:900;color:var(--lp-primary);font-family:Outfit,sans-serif;line-height:1}.lp-price-period{font-size:16px;color:var(--lp-gray-600)}.lp-pricing-desc{font-size:13px;color:var(--lp-gray-600);margin:0}.lp-pricing-features{list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:10px;flex:1}.lp-pricing-features li{font-size:14px;color:var(--lp-gray-700);padding-left:20px;position:relative}.lp-pricing-features li:before{content:\"\\2713\";position:absolute;left:0;color:var(--lp-primary);font-weight:700}.lp-btn-block{width:100%;justify-content:center;text-align:center}.lp-final-cta{background:linear-gradient(135deg,var(--lp-primary-dark, #152C4A) 0%,var(--lp-primary) 100%);padding:96px 0}.lp-cta-content{text-align:center}.lp-cta-content h2{font-size:40px;font-weight:900;color:#fff;margin:0 0 16px;font-family:Outfit,sans-serif;letter-spacing:-.02em}.lp-cta-content p{font-size:18px;color:#ffffffbf;margin:0 auto 36px;max-width:560px}.lp-cta-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}.lp-btn-white{background:#fff;color:var(--lp-primary);border:2px solid white;font-weight:700}.lp-btn-white:hover{background:#ffffffe6;transform:translateY(-1px)}.lp-btn-outline-white{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.5);font-weight:700}.lp-btn-outline-white:hover{background:#ffffff1a;border-color:#fff;transform:translateY(-1px)}.lp-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:48px;padding:64px 0 48px}.lp-footer-brand-col{display:flex;flex-direction:column;gap:16px}.lp-footer-brand-custom{display:flex;align-items:center;gap:10px}.lp-footer-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-footer-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-footer-brand-wordmark{display:flex;flex-direction:column;line-height:1.1}.lp-footer-wordmark-line1{font-size:15px;font-weight:800;color:#fff;font-family:Outfit,sans-serif}.lp-footer-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-footer-brand-default{display:flex;align-items:center;gap:10px}.lp-footer-tagline{font-size:14px;color:#ffffff80;line-height:1.6;margin:0;max-width:280px}.lp-footer-col{display:flex;flex-direction:column;gap:12px}.lp-footer-col h4{font-size:13px;font-weight:700;color:#fff;margin:0 0 4px;letter-spacing:.04em;text-transform:uppercase;font-family:Outfit,sans-serif}.lp-footer-col a{font-size:14px;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-col a:hover{color:#fff}.lp-footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:24px 0;font-size:13px;color:#ffffff59}@media (max-width: 768px){.lp-hero-visual{max-width:100%;margin-top:32px}.lp-workflow-road{flex-direction:column;align-items:center;gap:32px}.lp-road-line{display:none}.lp-waypoint-desc{max-width:260px}.lp-security-grid,.lp-testimonials-grid,.lp-pricing-grid{grid-template-columns:1fr}.lp-pricing-popular{transform:none}.lp-pricing-popular:hover{transform:translateY(-3px)}.lp-cta-content h2{font-size:28px}.lp-footer-grid{grid-template-columns:1fr 1fr;gap:32px;padding:48px 0 32px}}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
|
|
16702
|
+
}
|
|
16703
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: SpaLandingComponent, decorators: [{
|
|
16704
|
+
type: Component,
|
|
16705
|
+
args: [{ selector: 'spa-landing', standalone: false, template: "<!-- ===== NAVBAR ===== -->\n<nav class=\"lp-navbar\" [class.lp-navbar-scrolled]=\"navScrolled\">\n <div class=\"lp-container lp-nav-inner\">\n <!-- Brand: custom SVG override OR default img+name -->\n <div class=\"lp-nav-brand\" (click)=\"scrollToSection('lp-hero')\">\n <ng-container *ngIf=\"config.navBrand; else defaultNavBrand\">\n <span class=\"lp-nav-brand-svg\" [innerHTML]=\"safeNavBrandSvg\"></span>\n <div *ngIf=\"config.navBrand.wordmarkLine1\" class=\"lp-nav-brand-wordmark\">\n <span class=\"lp-nav-wordmark-line1\">{{ config.navBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.navBrand.wordmarkLine2\" class=\"lp-nav-wordmark-line2\">{{ config.navBrand.wordmarkLine2 }}</span>\n </div>\n </ng-container>\n <ng-template #defaultNavBrand>\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-nav-logo\">\n <span class=\"lp-nav-brand-text\">{{ config.appName }}</span>\n </ng-template>\n </div>\n <!-- Desktop nav links -->\n <div class=\"lp-nav-links\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n </div>\n <!-- Mobile menu overlay -->\n <div class=\"lp-mobile-menu\" [class.lp-mobile-menu-open]=\"mobileMenuOpen\">\n <a *ngFor=\"let link of config.navLinks\" (click)=\"scrollToSection(link.target)\">{{ link.label }}</a>\n <div class=\"lp-mobile-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n </div>\n <!-- Desktop auth -->\n <div class=\"lp-nav-auth\">\n <a class=\"lp-nav-login\" (click)=\"navigateTo('login')\">Log in</a>\n <button class=\"lp-btn lp-btn-primary lp-btn-sm\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Get Started</button>\n </div>\n <!-- Hamburger for mobile -->\n <button class=\"lp-hamburger\" (click)=\"toggleMobileMenu()\" [class.lp-hamburger-open]=\"mobileMenuOpen\">\n <span></span><span></span><span></span>\n </button>\n </div>\n</nav>\n\n<!-- ===== HERO ===== -->\n<section id=\"lp-hero\" class=\"lp-hero-bg\" [class.lp-hero-split]=\"config.hero.layout === 'split'\">\n <!-- Optional faded watermark SVG (e.g. company logo) -->\n <!-- Changed: wrapper div gets Angular scoping attribute; inner div holds innerHTML so svg size CSS applies to scoped .lp-hero-watermark-inner -->\n <div *ngIf=\"config.hero.watermarkSvg\" class=\"lp-hero-watermark\" aria-hidden=\"true\">\n <div class=\"lp-hero-watermark-inner\" [innerHTML]=\"safeHeroWatermarkSvg\"></div>\n </div>\n\n <div class=\"lp-container lp-hero-content\">\n <!-- Left / centered column: brand, headline, CTAs -->\n <div class=\"lp-hero-main\">\n <!-- App brand: logo + name + decorative divider -->\n <div class=\"lp-hero-brand\">\n <div class=\"lp-hero-brand-logo\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-hero-brand-icon\">\n <span class=\"lp-hero-brand-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-hero-divider\" aria-hidden=\"true\">\n <span class=\"lp-hero-divider-line\"></span>\n <span class=\"lp-hero-divider-dot\"></span>\n <span class=\"lp-hero-divider-line\"></span>\n </div>\n </div>\n <!-- Hero text: headline, subtitle, CTAs, trust signals -->\n <div class=\"lp-hero-text\">\n <div *ngIf=\"config.hero.badge\" class=\"lp-hero-badge\">{{ config.hero.badge }}</div>\n <h1>{{ config.hero.headline }} <span class=\"lp-gradient-text\">{{ config.hero.gradientText }}</span></h1>\n <p class=\"lp-hero-subtitle\">{{ config.hero.subtitle }}</p>\n <div class=\"lp-hero-ctas\">\n <button class=\"lp-btn lp-btn-primary lp-btn-lg\" (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">\n {{ config.hero.primaryCTA }}\n </button>\n <button class=\"lp-btn lp-btn-outline lp-btn-lg\" (click)=\"scrollToSection(config.hero.secondaryCTASection)\">\n {{ config.hero.secondaryCTA }}\n </button>\n </div>\n <!-- Trust signals with checkmark SVG icons -->\n <div class=\"lp-trust-signals\">\n <div *ngFor=\"let signal of config.hero.trustSignals\" class=\"lp-trust-item\">\n <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M20 6L9 17l-5-5\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n <span>{{ signal }}</span>\n </div>\n </div>\n </div>\n </div>\n <!-- App visual: always rendered when projected content exists (stacked below hero text) -->\n <!-- Changed: removed *ngIf so visual always renders below text; layout is always stacked/column -->\n <div class=\"lp-hero-visual\">\n <ng-content select=\"[lpHeroVisual]\"></ng-content>\n </div>\n </div>\n</section>\n\n<!-- ===== METRICS (optional) ===== -->\n<section *ngIf=\"config.metrics\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-metrics-grid\">\n <div *ngFor=\"let metric of config.metrics\" class=\"lp-metric-card\">\n <div class=\"lp-metric-value\">{{ metric.value }}</div>\n <div class=\"lp-metric-label\">{{ metric.label }}</div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FEATURES ===== -->\n<section id=\"lp-features\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.features.title }}</h2>\n <p>{{ config.features.subtitle }}</p>\n </div>\n <div class=\"lp-features-grid\">\n <div *ngFor=\"let feature of config.features.items\" class=\"lp-feature-card\">\n <!-- Icon tint: hex + '20' = ~12% opacity background -->\n <div class=\"lp-feature-icon\"\n [style.background]=\"iconBg(feature.color)\"\n [style.color]=\"feature.color\">\n <span class=\"material-icons\">{{ feature.icon }}</span>\n </div>\n <h3 class=\"lp-feature-title\">{{ feature.title }}</h3>\n <p class=\"lp-feature-desc\">{{ feature.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== WORKFLOW (optional) ===== -->\n<section *ngIf=\"config.workflow\" id=\"lp-workflow\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.workflow!.title }}</h2>\n <p *ngIf=\"config.workflow!.subtitle\">{{ config.workflow!.subtitle }}</p>\n </div>\n\n <!-- Road metaphor layout: horizontal road line with waypoints -->\n <div *ngIf=\"config.workflow!.style === 'road'\" class=\"lp-workflow-road\">\n <div class=\"lp-road-line\"></div>\n <div *ngFor=\"let step of config.workflow!.steps\" class=\"lp-waypoint\">\n <div class=\"lp-waypoint-marker\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <div class=\"lp-waypoint-number\">Step {{ step.number }}</div>\n <div class=\"lp-waypoint-title\">{{ step.title }}</div>\n <div class=\"lp-waypoint-desc\">{{ step.description }}</div>\n </div>\n </div>\n\n <!-- Default steps grid layout -->\n <div *ngIf=\"!config.workflow!.style || config.workflow!.style === 'steps'\" class=\"lp-steps-grid\">\n <div *ngFor=\"let step of config.workflow!.steps; let i = index\" class=\"lp-step-card\">\n <div class=\"lp-step-number\">{{ step.number }}</div>\n <div class=\"lp-step-icon\">\n <span class=\"material-icons\">{{ step.icon }}</span>\n </div>\n <h3 class=\"lp-step-title\">{{ step.title }}</h3>\n <p class=\"lp-step-desc\">{{ step.description }}</p>\n <!-- Connector line between steps, not after last -->\n <div *ngIf=\"i < config.workflow!.steps.length - 1\" class=\"lp-step-connector\"></div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== CUSTOM SECTION SLOT ===== -->\n<!-- Consumer projects app-specific sections here (fleet board, benefits rows, etc.)\n using [lpCustomSection] attribute on a wrapper element -->\n<ng-content select=\"[lpCustomSection]\"></ng-content>\n\n<!-- ===== MODULES (optional) ===== -->\n<section *ngIf=\"config.modules\" id=\"lp-modules\" class=\"lp-section lp-section-alt lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.modules!.title }}</h2>\n <p>{{ config.modules!.subtitle }}</p>\n </div>\n <div class=\"lp-modules-grid\">\n <div *ngFor=\"let group of config.modules!.groups\" class=\"lp-module-group\">\n <div class=\"lp-module-group-label\">{{ group.category }}</div>\n <div class=\"lp-module-tiles\">\n <div *ngFor=\"let tile of group.tiles\" class=\"lp-module-tile\">\n <span class=\"material-icons\">{{ tile.icon }}</span>\n <span>{{ tile.name }}</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== SECURITY (optional \u2014 dark background) ===== -->\n<section *ngIf=\"config.security\" class=\"lp-section lp-section-dark lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header lp-header-light\">\n <h2>{{ config.security!.title }}</h2>\n <p>{{ config.security!.subtitle }}</p>\n </div>\n <div class=\"lp-security-grid\">\n <div *ngFor=\"let item of config.security!.items\" class=\"lp-security-card\">\n <div class=\"lp-security-icon\">\n <span class=\"material-icons\">{{ item.icon }}</span>\n </div>\n <h3>{{ item.title }}</h3>\n <p>{{ item.description }}</p>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== TESTIMONIALS (optional) ===== -->\n<section *ngIf=\"config.testimonials\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.testimonials!.title }}</h2>\n <p *ngIf=\"config.testimonials!.subtitle\">{{ config.testimonials!.subtitle }}</p>\n </div>\n <div class=\"lp-testimonials-grid\">\n <div *ngFor=\"let item of config.testimonials!.items\" class=\"lp-testimonial-card\">\n <div class=\"lp-stars\">\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n <span class=\"material-icons\">star</span>\n </div>\n <p class=\"lp-testimonial-quote\">\"{{ item.quote }}\"</p>\n <div class=\"lp-testimonial-author\">\n <div class=\"lp-author-avatar\">{{ item.authorInitials }}</div>\n <div>\n <div class=\"lp-author-name\">{{ item.authorName }}</div>\n <div class=\"lp-author-role\">{{ item.authorRole }}</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== PRICING (optional) ===== -->\n<section *ngIf=\"config.pricing\" id=\"lp-pricing\" class=\"lp-section lp-animate\">\n <div class=\"lp-container\">\n <div class=\"lp-section-header\">\n <h2>{{ config.pricing!.title }}</h2>\n <p *ngIf=\"config.pricing!.subtitle\">{{ config.pricing!.subtitle }}</p>\n </div>\n <div class=\"lp-pricing-grid\">\n <div *ngFor=\"let plan of config.pricing!.plans\"\n class=\"lp-pricing-card\"\n [class.lp-pricing-popular]=\"plan.popular\">\n <div *ngIf=\"plan.popular\" class=\"lp-popular-badge\">Most Popular</div>\n <div class=\"lp-pricing-header\">\n <h3>{{ plan.name }}</h3>\n <div class=\"lp-pricing-price\">\n <span class=\"lp-price-amount\">{{ plan.price }}</span>\n <span *ngIf=\"plan.period\" class=\"lp-price-period\">{{ plan.period }}</span>\n </div>\n <p class=\"lp-pricing-desc\">{{ plan.description }}</p>\n </div>\n <ul class=\"lp-pricing-features\">\n <li *ngFor=\"let feature of plan.features\">{{ feature }}</li>\n </ul>\n <!-- Popular plans get primary button, others get outline -->\n <button *ngIf=\"plan.popular\"\n class=\"lp-btn lp-btn-primary lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n <button *ngIf=\"!plan.popular\"\n class=\"lp-btn lp-btn-outline lp-btn-block\"\n (click)=\"plan.ctaRoute ? navigateTo(plan.ctaRoute) : scrollToSection(plan.ctaSection || '')\">\n {{ plan.ctaLabel }}\n </button>\n </div>\n </div>\n </div>\n</section>\n\n<!-- ===== FINAL CTA BANNER (optional) ===== -->\n<section *ngIf=\"config.finalCta\" class=\"lp-final-cta lp-animate\">\n <div class=\"lp-container lp-cta-content\">\n <h2>{{ config.finalCta!.headline }}</h2>\n <p>{{ config.finalCta!.subtitle }}</p>\n <div class=\"lp-cta-buttons\">\n <button class=\"lp-btn lp-btn-white lp-btn-lg\"\n (click)=\"navigateTo(config.finalCta!.primaryCTARoute || 'signup')\">\n {{ config.finalCta!.primaryCTA }}\n </button>\n <button *ngIf=\"config.finalCta!.secondaryCTA\"\n class=\"lp-btn lp-btn-outline-white lp-btn-lg\"\n (click)=\"scrollToSection(config.finalCta!.secondaryCTASection || '')\">\n {{ config.finalCta!.secondaryCTA }}\n </button>\n </div>\n </div>\n</section>\n\n<!-- ===== FOOTER ===== -->\n<footer class=\"lp-footer\" id=\"lp-footer\">\n <div class=\"lp-container\">\n\n <!-- Rich multi-column footer when footerColumns provided -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length; else simpleFooter\" class=\"lp-footer-grid\">\n <!-- Brand column -->\n <div class=\"lp-footer-brand-col\">\n <!-- Custom SVG footer brand OR default img+name -->\n <ng-container *ngIf=\"config.footerBrand; else defaultFooterBrand\">\n <div class=\"lp-footer-brand-custom\">\n <span class=\"lp-footer-brand-svg\" [innerHTML]=\"safeFooterBrandSvg\"></span>\n <div *ngIf=\"config.footerBrand.wordmarkLine1\" class=\"lp-footer-brand-wordmark\">\n <span class=\"lp-footer-wordmark-line1\">{{ config.footerBrand.wordmarkLine1 }}</span>\n <span *ngIf=\"config.footerBrand.wordmarkLine2\" class=\"lp-footer-wordmark-line2\">{{ config.footerBrand.wordmarkLine2 }}</span>\n </div>\n </div>\n </ng-container>\n <ng-template #defaultFooterBrand>\n <div class=\"lp-footer-brand-default\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n </ng-template>\n <p *ngIf=\"config.footerTagline\" class=\"lp-footer-tagline\">{{ config.footerTagline }}</p>\n </div>\n <!-- Link columns -->\n <div *ngFor=\"let col of config.footerColumns\" class=\"lp-footer-col\">\n <h4>{{ col.title }}</h4>\n <a *ngFor=\"let link of col.links\"\n (click)=\"link.section ? scrollToSection(link.section) : (link.route ? navigateTo(link.route) : null)\">\n {{ link.label }}\n </a>\n </div>\n </div>\n\n <!-- Simple single-row footer (default) -->\n <ng-template #simpleFooter>\n <div class=\"lp-footer-inner\">\n <div class=\"lp-footer-brand\">\n <img [src]=\"config.logoSrc\" [alt]=\"config.appName\" class=\"lp-footer-logo\">\n <span class=\"lp-footer-name\">{{ config.appName }}</span>\n </div>\n <div class=\"lp-footer-links\">\n <a (click)=\"navigateTo('login')\">Log in</a>\n <a (click)=\"navigateTo(config.hero.primaryCTARoute || 'signup')\">Sign up</a>\n </div>\n <div class=\"lp-footer-copy\">\n © {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.\n </div>\n </div>\n </ng-template>\n\n <!-- Footer bottom bar (shown for both layouts) -->\n <div *ngIf=\"config.footerColumns && config.footerColumns.length\" class=\"lp-footer-bottom\">\n <span>© {{ currentYear }} {{ config.footerCompany || 'alsquare technologies' }}. All rights reserved.</span>\n </div>\n </div>\n</footer>\n", styles: ["@import\"https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap\";:host{--lp-primary: #1E3A5F;--lp-primary-dark: #152C4A;--lp-primary-light: #EBF0F7;--lp-secondary: #F59E0B;--lp-secondary-light:#FEF3C7;--lp-dark: #0D1117;--lp-gray-800: #1F2937;--lp-gray-700: #374151;--lp-gray-600: #4B5563;--lp-gray-400: #9CA3AF;--lp-gray-200: #E5E7EB;--lp-gray-100: #F3F4F6;--lp-gray-50: #F9FAFB;--lp-white: #FFFFFF;--lp-radius: 12px;--lp-radius-sm: 8px;--lp-radius-lg: 20px;--lp-radius-xl: 28px;--lp-shadow-xs: 0 1px 2px rgba(0,0,0,.05);--lp-shadow: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.05);--lp-shadow-md: 0 4px 8px rgba(0,0,0,.06), 0 2px 4px rgba(0,0,0,.04);--lp-shadow-lg: 0 12px 28px rgba(0,0,0,.1), 0 4px 8px rgba(0,0,0,.06);--lp-shadow-xl: 0 20px 40px rgba(0,0,0,.12), 0 8px 16px rgba(0,0,0,.07);--lp-ease: cubic-bezier(.4, 0, .2, 1);--lp-spring: cubic-bezier(.34, 1.56, .64, 1);display:block;font-family:Outfit,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;color:var(--lp-gray-700);line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased}.lp-container{max-width:1200px;margin:0 auto;padding:0 32px}.lp-navbar{position:fixed;top:0;left:0;right:0;z-index:1000;padding:18px 0;transition:background .3s var(--lp-ease),padding .3s var(--lp-ease),box-shadow .3s var(--lp-ease)}.lp-navbar-scrolled{background:#ffffffeb;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);box-shadow:0 1px #0000000f,0 2px 8px #0000000a;padding:12px 0}.lp-nav-inner{display:flex;align-items:center;justify-content:space-between}.lp-nav-brand{display:flex;align-items:center;gap:10px;cursor:pointer;text-decoration:none}.lp-nav-logo{width:30px;height:30px;border-radius:7px;object-fit:contain}.lp-nav-brand-text{font-size:18px;font-weight:700;color:var(--lp-primary);letter-spacing:-.01em}.lp-nav-links{display:flex;align-items:center;gap:36px}.lp-nav-links a{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none;letter-spacing:.01em}.lp-nav-links a:hover{color:var(--lp-primary)}.lp-nav-auth{display:flex;align-items:center;gap:12px}.lp-nav-login{font-size:14px;font-weight:500;color:var(--lp-gray-600);cursor:pointer;transition:color .2s;text-decoration:none}.lp-nav-login:hover{color:var(--lp-primary)}.lp-hamburger{display:none;flex-direction:column;gap:5px;background:none;border:none;cursor:pointer;padding:4px;z-index:1001}.lp-hamburger span{display:block;width:22px;height:2px;background:var(--lp-gray-700);border-radius:2px;transition:transform .3s var(--lp-ease),opacity .3s}.lp-hamburger-open span:nth-child(1){transform:translateY(7px) rotate(45deg)}.lp-hamburger-open span:nth-child(2){opacity:0}.lp-hamburger-open span:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.lp-mobile-menu{display:none}.lp-mobile-auth{display:flex;flex-direction:column;gap:12px;margin-top:24px;padding-top:24px;border-top:1px solid var(--lp-gray-200)}.lp-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:Outfit,sans-serif;font-size:14px;font-weight:600;padding:10px 22px;border-radius:var(--lp-radius-sm);border:none;cursor:pointer;transition:all .25s var(--lp-ease);text-decoration:none;white-space:nowrap;letter-spacing:.01em}.lp-btn-primary{background:var(--lp-primary);color:var(--lp-white);box-shadow:0 1px 3px #00000026,inset 0 1px #ffffff1f}.lp-btn-primary:hover{background:var(--lp-primary-dark);transform:translateY(-1px);box-shadow:0 6px 16px #0003,inset 0 1px #ffffff1f}.lp-btn-primary:active{transform:translateY(0)}.lp-btn-outline{background:transparent;color:var(--lp-primary);border:1.5px solid var(--lp-primary)}.lp-btn-outline:hover{background:var(--lp-primary);color:var(--lp-white);transform:translateY(-1px);box-shadow:0 4px 12px #00000026}.lp-btn-sm{padding:7px 16px;font-size:13px}.lp-btn-lg{padding:14px 32px;font-size:15px;border-radius:var(--lp-radius)}.lp-btn-block{width:100%}.lp-hero-bg{position:relative;overflow:hidden;padding:140px 0 100px;background:radial-gradient(ellipse 80% 60% at 60% -10%,color-mix(in srgb,var(--lp-primary) 8%,transparent) 0%,transparent 70%),radial-gradient(ellipse 60% 50% at -10% 80%,color-mix(in srgb,var(--lp-secondary) 6%,transparent) 0%,transparent 70%),linear-gradient(160deg,var(--lp-primary-light) 0%,#FAFBFF 45%,var(--lp-secondary-light) 100%);min-height:100vh;display:flex;align-items:center}.lp-hero-bg:before{content:\"\";position:absolute;inset:0;background-image:radial-gradient(circle,rgba(0,0,0,.04) 1px,transparent 1px);background-size:32px 32px;pointer-events:none;-webkit-mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%);mask-image:radial-gradient(ellipse 80% 80% at 50% 50%,black 40%,transparent 100%)}.lp-hero-bg:after{content:\"\";position:absolute;width:700px;height:700px;border-radius:50%;background:color-mix(in srgb,var(--lp-primary) 5%,transparent);top:-200px;right:-150px;filter:blur(100px);pointer-events:none}.lp-hero-content{position:relative;z-index:1;text-align:center;width:100%}.lp-hero-badge{display:inline-flex;align-items:center;gap:6px;background:color-mix(in srgb,var(--lp-primary) 10%,transparent);color:var(--lp-primary);border:1px solid color-mix(in srgb,var(--lp-primary) 20%,transparent);border-radius:100px;padding:6px 16px;font-size:13px;font-weight:600;margin-bottom:20px;letter-spacing:.02em}.lp-hero-brand{margin-bottom:20px;text-align:center}.lp-hero-brand-logo{display:inline-flex;align-items:center;gap:14px;margin-bottom:12px}.lp-hero-brand-icon{height:52px;width:52px;border-radius:14px;object-fit:contain;box-shadow:0 4px 12px #0000001f}.lp-hero-brand-name{font-size:38px;font-weight:800;color:var(--lp-primary);letter-spacing:-.03em;font-family:Outfit,sans-serif}.lp-hero-divider{display:flex;align-items:center;justify-content:center;gap:10px}.lp-hero-divider-line{display:block;height:1px;width:40px;background:linear-gradient(90deg,transparent,var(--lp-primary),transparent);opacity:.3}.lp-hero-divider-dot{display:block;height:5px;width:5px;border-radius:50%;background:var(--lp-secondary);opacity:.8}.lp-hero-text{max-width:760px;margin:0 auto 64px}.lp-hero-text h1{font-family:Outfit,sans-serif;font-size:68px;font-weight:900;line-height:1.05;color:var(--lp-dark);margin:0 0 24px;letter-spacing:-.03em}.lp-gradient-text{background:linear-gradient(135deg,var(--lp-primary) 0%,var(--lp-secondary) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.lp-hero-subtitle{font-size:19px;color:var(--lp-gray-600);line-height:1.65;font-weight:400;max-width:560px;margin:0 auto 36px}.lp-hero-ctas{display:flex;align-items:center;justify-content:center;gap:14px;margin-bottom:32px;flex-wrap:wrap}.lp-trust-signals{display:flex;align-items:center;justify-content:center;gap:28px;flex-wrap:wrap}.lp-trust-item{display:flex;align-items:center;gap:7px;font-size:13px;font-weight:500;color:var(--lp-gray-500, #6B7280)}.lp-trust-item svg{color:var(--lp-primary);flex-shrink:0}.lp-section{padding:100px 0;background:var(--lp-white)}.lp-section-alt{background:var(--lp-gray-50)}.lp-section-header{text-align:center;margin-bottom:64px}.lp-section-header h2{font-family:Outfit,sans-serif;font-size:42px;font-weight:800;color:var(--lp-dark);margin:0 0 14px;letter-spacing:-.025em;line-height:1.15}.lp-section-header p{font-size:17px;color:var(--lp-gray-600);max-width:540px;margin:0 auto;line-height:1.65}.lp-features-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.lp-feature-card{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:36px 28px;transition:transform .25s var(--lp-ease),box-shadow .25s var(--lp-ease);border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);position:relative;overflow:hidden}.lp-feature-card:before{content:\"\";position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,var(--lp-primary),var(--lp-secondary));transform:scaleX(0);transform-origin:left;transition:transform .3s var(--lp-ease);border-radius:3px 3px 0 0}.lp-feature-card:hover{transform:translateY(-5px);box-shadow:var(--lp-shadow-xl);border-color:transparent}.lp-feature-card:hover:before{transform:scaleX(1)}.lp-feature-icon{width:52px;height:52px;border-radius:var(--lp-radius-sm);display:flex;align-items:center;justify-content:center;margin-bottom:20px}.lp-feature-icon .material-icons{font-size:26px}.lp-feature-title{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-dark);margin:0 0 10px;letter-spacing:-.01em}.lp-feature-desc{font-size:14px;color:var(--lp-gray-600);line-height:1.65;margin:0}.lp-steps-grid{display:flex;align-items:flex-start;justify-content:space-between;position:relative;gap:16px}.lp-step-card{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;padding:0 8px}.lp-step-number{font-size:11px;font-weight:700;color:var(--lp-secondary);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px}.lp-step-icon{width:56px;height:56px;border-radius:50%;background:var(--lp-primary);display:flex;align-items:center;justify-content:center;margin-bottom:18px;box-shadow:0 4px 14px color-mix(in srgb,var(--lp-primary) 30%,transparent);border:3px solid var(--lp-white);position:relative;z-index:1}.lp-step-icon .material-icons{font-size:24px;color:var(--lp-white)}.lp-step-title{font-family:Outfit,sans-serif;font-size:15px;font-weight:700;color:var(--lp-dark);margin:0 0 8px;letter-spacing:-.01em}.lp-step-desc{font-size:13px;color:var(--lp-gray-600);line-height:1.55;margin:0}.lp-step-connector{position:absolute;top:40px;left:60%;width:80%;height:2px;background:repeating-linear-gradient(90deg,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 0px,color-mix(in srgb,var(--lp-secondary) 50%,transparent) 10px,transparent 10px,transparent 18px);pointer-events:none;z-index:0}.lp-metrics-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--lp-gray-200);border-radius:var(--lp-radius-lg);overflow:hidden;background:var(--lp-white);box-shadow:var(--lp-shadow)}.lp-metric-card{text-align:center;padding:40px 24px;border-right:1px solid var(--lp-gray-200);transition:background .2s}.lp-metric-card:last-child{border-right:none}.lp-metric-card:hover{background:var(--lp-gray-50)}.lp-metric-value{font-family:Outfit,sans-serif;font-size:48px;font-weight:900;color:var(--lp-primary);line-height:1;letter-spacing:-.03em;margin-bottom:8px}.lp-metric-label{font-size:14px;font-weight:500;color:var(--lp-gray-600)}.lp-modules-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px}.lp-module-group{background:var(--lp-white);border-radius:var(--lp-radius-lg);padding:28px 28px 24px;border:1px solid var(--lp-gray-200);box-shadow:var(--lp-shadow-xs);transition:box-shadow .2s}.lp-module-group:hover{box-shadow:var(--lp-shadow-md)}.lp-module-group-label{font-family:Outfit,sans-serif;font-size:11px;font-weight:700;color:var(--lp-primary);margin-bottom:16px;text-transform:uppercase;letter-spacing:1px}.lp-module-tiles{display:flex;flex-wrap:wrap;gap:8px}.lp-module-tile{display:inline-flex;align-items:center;gap:6px;background:var(--lp-gray-50);border:1px solid var(--lp-gray-200);padding:7px 13px;border-radius:100px;font-size:12px;font-weight:600;color:var(--lp-gray-700);transition:all .2s var(--lp-ease);cursor:default}.lp-module-tile:hover{background:color-mix(in srgb,var(--lp-primary) 6%,transparent);border-color:color-mix(in srgb,var(--lp-primary) 25%,transparent);color:var(--lp-primary);transform:translateY(-1px)}.lp-module-tile .material-icons{font-size:15px;color:var(--lp-primary)}.lp-footer{background:var(--lp-dark);padding:44px 0 36px;border-top:1px solid rgba(255,255,255,.04)}.lp-footer-inner{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}.lp-footer-brand{display:flex;align-items:center;gap:10px}.lp-footer-logo{width:30px;height:30px;border-radius:7px;object-fit:contain;opacity:.9}.lp-footer-name{font-family:Outfit,sans-serif;font-size:17px;font-weight:700;color:var(--lp-white);letter-spacing:-.01em}.lp-footer-links{display:flex;gap:28px}.lp-footer-links a{font-size:14px;font-weight:500;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-links a:hover{color:var(--lp-white)}.lp-footer-copy{font-size:13px;color:#ffffff59;width:100%;text-align:center;padding-top:20px;margin-top:4px;border-top:1px solid rgba(255,255,255,.06)}.lp-animate{opacity:0;transform:translateY(28px);transition:opacity .65s var(--lp-ease),transform .65s var(--lp-ease)}.lp-visible{opacity:1;transform:translateY(0)}.lp-visible .lp-feature-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-feature-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-feature-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-feature-card:nth-child(4){transition-delay:.18s}.lp-visible .lp-feature-card:nth-child(5){transition-delay:.24s}.lp-visible .lp-feature-card:nth-child(6){transition-delay:.3s}.lp-visible .lp-step-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-step-card:nth-child(2){transition-delay:80ms}.lp-visible .lp-step-card:nth-child(3){transition-delay:.16s}.lp-visible .lp-step-card:nth-child(4){transition-delay:.24s}.lp-visible .lp-metric-card:nth-child(1){transition-delay:0ms}.lp-visible .lp-metric-card:nth-child(2){transition-delay:60ms}.lp-visible .lp-metric-card:nth-child(3){transition-delay:.12s}.lp-visible .lp-metric-card:nth-child(4){transition-delay:.18s}@media (max-width: 1024px){.lp-hero-text h1{font-size:52px}.lp-section-header h2{font-size:36px}.lp-features-grid,.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card:nth-child(2){border-right:none}.lp-metrics-grid .lp-metric-card:nth-child(3){border-top:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:nth-child(4){border-top:1px solid var(--lp-gray-200)}.lp-steps-grid{flex-wrap:wrap;justify-content:center;gap:40px}.lp-step-connector{display:none}}@media (max-width: 767px){.lp-container{padding:0 20px}.lp-nav-links{display:none}.lp-hamburger{display:flex}.lp-mobile-auth{display:none}.lp-mobile-menu{display:none;position:fixed;inset:0;background:#fffffff7;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);flex-direction:column;align-items:center;justify-content:center;gap:28px;z-index:999}.lp-mobile-menu-open{display:flex}.lp-mobile-menu a{font-size:22px;font-weight:700;color:var(--lp-dark);cursor:pointer;text-decoration:none;letter-spacing:-.01em}.lp-hero-bg{padding:100px 0 60px;min-height:auto}.lp-hero-text h1{font-size:38px}.lp-hero-brand-name{font-size:28px}.lp-hero-subtitle{font-size:16px}.lp-hero-ctas{flex-direction:column}.lp-hero-ctas .lp-btn{width:100%;justify-content:center}.lp-trust-signals{gap:16px}.lp-section{padding:72px 0}.lp-section-header{margin-bottom:40px}.lp-section-header h2{font-size:28px}.lp-features-grid{grid-template-columns:1fr}.lp-metrics-grid{grid-template-columns:repeat(2,1fr)}.lp-metrics-grid .lp-metric-card{border-right:none;border-bottom:1px solid var(--lp-gray-200)}.lp-metrics-grid .lp-metric-card:last-child{border-bottom:none}.lp-metric-value{font-size:36px}.lp-modules-grid{grid-template-columns:1fr}.lp-steps-grid{flex-direction:column;align-items:center;gap:32px}.lp-step-connector{display:none}.lp-footer-inner{flex-direction:column;align-items:center;text-align:center}}.lp-nav-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-nav-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-nav-brand-wordmark{display:flex;flex-direction:column;line-height:1.1;margin-left:8px}.lp-nav-wordmark-line1{font-size:15px;font-weight:800;color:#1a3a7a;letter-spacing:-.01em;font-family:Outfit,sans-serif}.lp-nav-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-hero-bg{position:relative}.lp-hero-watermark{position:absolute;inset:0;display:flex;align-items:flex-start;justify-content:center;padding-top:40px;pointer-events:none;z-index:1}.lp-hero-watermark-inner{width:700px;height:700px;opacity:.04;overflow:hidden;flex-shrink:0}@media (min-width: 768px){.lp-hero-watermark-inner{width:800px;height:800px}}.lp-hero-content{display:flex;flex-direction:column;align-items:center}.lp-hero-main{z-index:1;width:100%}.lp-hero-visual{position:relative;z-index:1;width:100%;max-width:900px;margin:48px auto 0}.lp-workflow-road{position:relative;display:flex;justify-content:space-between;align-items:flex-start;padding:48px 0 24px;gap:16px}.lp-road-line{position:absolute;top:72px;left:6%;right:6%;height:3px;background:linear-gradient(90deg,var(--lp-primary-light),var(--lp-primary),var(--lp-primary-light));border-radius:2px;opacity:.4}.lp-waypoint{flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1}.lp-waypoint-marker{width:52px;height:52px;border-radius:50%;background:var(--lp-primary);color:#fff;display:flex;align-items:center;justify-content:center;margin-bottom:12px;box-shadow:0 4px 16px color-mix(in srgb,var(--lp-primary) 35%,transparent);transition:transform .25s cubic-bezier(.34,1.56,.64,1)}.lp-waypoint:hover .lp-waypoint-marker{transform:translateY(-4px) scale(1.08)}.lp-waypoint-marker .material-icons{font-size:22px}.lp-waypoint-number{font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--lp-primary);margin-bottom:6px;opacity:.7}.lp-waypoint-title{font-size:14px;font-weight:700;color:var(--lp-dark);margin-bottom:6px;font-family:Outfit,sans-serif}.lp-waypoint-desc{font-size:12px;color:var(--lp-gray-600);line-height:1.5;max-width:140px}.lp-section-dark{background:var(--lp-dark);color:#fff}.lp-section-dark.lp-section{padding:96px 0}.lp-header-light h2{color:#fff}.lp-header-light p{color:#ffffffa6}.lp-security-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}.lp-security-card{background:#ffffff0d;border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:32px 28px;transition:background .25s ease}.lp-security-card:hover{background:#ffffff14}.lp-security-icon{width:52px;height:52px;border-radius:14px;background:#ffffff1a;display:flex;align-items:center;justify-content:center;margin-bottom:20px;color:#fff}.lp-security-icon .material-icons{font-size:24px}.lp-security-card h3{font-size:18px;font-weight:700;color:#fff;margin:0 0 12px;font-family:Outfit,sans-serif}.lp-security-card p{font-size:14px;color:#fff9;line-height:1.65;margin:0}.lp-testimonials-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px}.lp-testimonial-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:32px 28px;display:flex;flex-direction:column;gap:16px;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-testimonial-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-2px)}.lp-stars{display:flex;gap:2px;color:var(--lp-secondary)}.lp-stars .material-icons{font-size:18px}.lp-testimonial-quote{font-size:15px;color:var(--lp-gray-700);line-height:1.65;font-style:italic;margin:0;flex:1}.lp-testimonial-author{display:flex;align-items:center;gap:12px}.lp-author-avatar{width:44px;height:44px;border-radius:50%;background:var(--lp-primary);color:#fff;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-family:Outfit,sans-serif}.lp-author-name{font-size:14px;font-weight:700;color:var(--lp-dark)}.lp-author-role{font-size:12px;color:var(--lp-gray-400)}.lp-pricing-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px;align-items:start}.lp-pricing-card{background:#fff;border:1px solid var(--lp-gray-200);border-radius:20px;padding:36px 28px 28px;position:relative;display:flex;flex-direction:column;box-shadow:0 1px 3px #00000012,0 1px 2px #0000000d;transition:box-shadow .25s ease,transform .25s ease}.lp-pricing-card:hover{box-shadow:0 4px 8px #0000000f;transform:translateY(-3px)}.lp-pricing-popular{border-color:var(--lp-primary);box-shadow:0 0 0 2px var(--lp-primary);transform:scale(1.03)}.lp-pricing-popular:hover{transform:scale(1.03) translateY(-3px)}.lp-popular-badge{position:absolute;top:-12px;left:50%;transform:translate(-50%);background:var(--lp-primary);color:#fff;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;padding:4px 14px;border-radius:100px;white-space:nowrap;font-family:Outfit,sans-serif}.lp-pricing-header{margin-bottom:24px}.lp-pricing-header h3{font-size:20px;font-weight:800;color:var(--lp-dark);margin:0 0 12px;font-family:Outfit,sans-serif}.lp-pricing-price{display:flex;align-items:baseline;gap:4px;margin-bottom:8px}.lp-price-amount{font-size:40px;font-weight:900;color:var(--lp-primary);font-family:Outfit,sans-serif;line-height:1}.lp-price-period{font-size:16px;color:var(--lp-gray-600)}.lp-pricing-desc{font-size:13px;color:var(--lp-gray-600);margin:0}.lp-pricing-features{list-style:none;padding:0;margin:0 0 28px;display:flex;flex-direction:column;gap:10px;flex:1}.lp-pricing-features li{font-size:14px;color:var(--lp-gray-700);padding-left:20px;position:relative}.lp-pricing-features li:before{content:\"\\2713\";position:absolute;left:0;color:var(--lp-primary);font-weight:700}.lp-btn-block{width:100%;justify-content:center;text-align:center}.lp-final-cta{background:linear-gradient(135deg,var(--lp-primary-dark, #152C4A) 0%,var(--lp-primary) 100%);padding:96px 0}.lp-cta-content{text-align:center}.lp-cta-content h2{font-size:40px;font-weight:900;color:#fff;margin:0 0 16px;font-family:Outfit,sans-serif;letter-spacing:-.02em}.lp-cta-content p{font-size:18px;color:#ffffffbf;margin:0 auto 36px;max-width:560px}.lp-cta-buttons{display:flex;gap:16px;justify-content:center;flex-wrap:wrap}.lp-btn-white{background:#fff;color:var(--lp-primary);border:2px solid white;font-weight:700}.lp-btn-white:hover{background:#ffffffe6;transform:translateY(-1px)}.lp-btn-outline-white{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.5);font-weight:700}.lp-btn-outline-white:hover{background:#ffffff1a;border-color:#fff;transform:translateY(-1px)}.lp-footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:48px;padding:64px 0 48px}.lp-footer-brand-col{display:flex;flex-direction:column;gap:16px}.lp-footer-brand-custom{display:flex;align-items:center;gap:10px}.lp-footer-brand-svg{display:flex;align-items:center;width:36px;height:36px;flex-shrink:0}.lp-footer-brand-svg ::ng-deep svg{width:36px;height:36px;display:block}.lp-footer-brand-wordmark{display:flex;flex-direction:column;line-height:1.1}.lp-footer-wordmark-line1{font-size:15px;font-weight:800;color:#fff;font-family:Outfit,sans-serif}.lp-footer-wordmark-line2{font-size:9px;font-weight:600;letter-spacing:.12em;color:#e8a020;text-transform:uppercase}.lp-footer-brand-default{display:flex;align-items:center;gap:10px}.lp-footer-tagline{font-size:14px;color:#ffffff80;line-height:1.6;margin:0;max-width:280px}.lp-footer-col{display:flex;flex-direction:column;gap:12px}.lp-footer-col h4{font-size:13px;font-weight:700;color:#fff;margin:0 0 4px;letter-spacing:.04em;text-transform:uppercase;font-family:Outfit,sans-serif}.lp-footer-col a{font-size:14px;color:#ffffff80;cursor:pointer;text-decoration:none;transition:color .2s}.lp-footer-col a:hover{color:#fff}.lp-footer-bottom{border-top:1px solid rgba(255,255,255,.08);padding:24px 0;font-size:13px;color:#ffffff59}@media (max-width: 768px){.lp-hero-visual{max-width:100%;margin-top:32px}.lp-workflow-road{flex-direction:column;align-items:center;gap:32px}.lp-road-line{display:none}.lp-waypoint-desc{max-width:260px}.lp-security-grid,.lp-testimonials-grid,.lp-pricing-grid{grid-template-columns:1fr}.lp-pricing-popular{transform:none}.lp-pricing-popular:hover{transform:translateY(-3px)}.lp-cta-content h2{font-size:28px}.lp-footer-grid{grid-template-columns:1fr 1fr;gap:32px;padding:48px 0 32px}}\n"] }]
|
|
16706
|
+
}], ctorParameters: () => [{ type: i1$2.Router }, { type: AuthService }, { type: i0.ElementRef }, { type: i1$5.DomSanitizer }], propDecorators: { config: [{
|
|
16707
|
+
type: Input
|
|
16708
|
+
}], onWindowScroll: [{
|
|
16709
|
+
type: HostListener,
|
|
16710
|
+
args: ['window:scroll']
|
|
16711
|
+
}] } });
|
|
16712
|
+
|
|
16513
16713
|
class TinSpaModule {
|
|
16514
16714
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
16515
16715
|
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, declarations: [TinSpaComponent, TextComponent, TextMaskComponent, TextAreaComponent, TextSingleComponent, CheckComponent, DateComponent, DatetimeComponent, LabelComponent, SelectComponent,
|
|
@@ -16528,7 +16728,8 @@ class TinSpaModule {
|
|
|
16528
16728
|
AppModelsComponent, LoanProductsComponent, LoansComponent, LoanPaymentsComponent, WelcomeComponent, // Tin-SPA modules welcome
|
|
16529
16729
|
TermsDialogComponent, PrivacyDialogComponent, // Changed: Added terms and privacy dialogs
|
|
16530
16730
|
ChartsComponent, // Changed: Added ChartsComponent for config-driven chart rendering
|
|
16531
|
-
FeatureDirective // Added: *spaFeature structural directive for plan-based feature gating
|
|
16731
|
+
FeatureDirective, // Added: *spaFeature structural directive for plan-based feature gating
|
|
16732
|
+
SpaLandingComponent // Added: Config-driven landing page component
|
|
16532
16733
|
], imports: [SpaMatModule,
|
|
16533
16734
|
HttpClientModule,
|
|
16534
16735
|
CurrencyInputModule,
|
|
@@ -16580,7 +16781,8 @@ class TinSpaModule {
|
|
|
16580
16781
|
TermsDialogComponent, PrivacyDialogComponent, // Changed: Added terms and privacy dialogs to exports
|
|
16581
16782
|
SelectLiteComponent, // Changed: Exported so pages outside TinSpaModule can use spa-select-lite
|
|
16582
16783
|
ChartsComponent, // Changed: Exported ChartsComponent for consumer apps
|
|
16583
|
-
FeatureDirective // Added: Exported *spaFeature directive for consumer apps
|
|
16784
|
+
FeatureDirective, // Added: Exported *spaFeature directive for consumer apps
|
|
16785
|
+
SpaLandingComponent // Added: Exported config-driven landing page component
|
|
16584
16786
|
] }); }
|
|
16585
16787
|
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.14", ngImport: i0, type: TinSpaModule, providers: [
|
|
16586
16788
|
{ provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true },
|
|
@@ -16620,7 +16822,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
16620
16822
|
AppModelsComponent, LoanProductsComponent, LoansComponent, LoanPaymentsComponent, WelcomeComponent, // Tin-SPA modules welcome
|
|
16621
16823
|
TermsDialogComponent, PrivacyDialogComponent, // Changed: Added terms and privacy dialogs
|
|
16622
16824
|
ChartsComponent, // Changed: Added ChartsComponent for config-driven chart rendering
|
|
16623
|
-
FeatureDirective // Added: *spaFeature structural directive for plan-based feature gating
|
|
16825
|
+
FeatureDirective, // Added: *spaFeature structural directive for plan-based feature gating
|
|
16826
|
+
SpaLandingComponent // Added: Config-driven landing page component
|
|
16624
16827
|
],
|
|
16625
16828
|
imports: [
|
|
16626
16829
|
SpaMatModule,
|
|
@@ -16676,7 +16879,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
16676
16879
|
TermsDialogComponent, PrivacyDialogComponent, // Changed: Added terms and privacy dialogs to exports
|
|
16677
16880
|
SelectLiteComponent, // Changed: Exported so pages outside TinSpaModule can use spa-select-lite
|
|
16678
16881
|
ChartsComponent, // Changed: Exported ChartsComponent for consumer apps
|
|
16679
|
-
FeatureDirective // Added: Exported *spaFeature directive for consumer apps
|
|
16882
|
+
FeatureDirective, // Added: Exported *spaFeature directive for consumer apps
|
|
16883
|
+
SpaLandingComponent // Added: Exported config-driven landing page component
|
|
16680
16884
|
],
|
|
16681
16885
|
providers: [
|
|
16682
16886
|
{ provide: HTTP_INTERCEPTORS, useClass: LoaderInterceptor, multi: true },
|
|
@@ -16724,45 +16928,17 @@ class LoginComponent {
|
|
|
16724
16928
|
else {
|
|
16725
16929
|
this.redirectPath = "home";
|
|
16726
16930
|
}
|
|
16727
|
-
// Changed:
|
|
16728
|
-
|
|
16729
|
-
|
|
16730
|
-
this.
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
if (this.authService.hasRefreshToken()) {
|
|
16735
|
-
this.isProcessing = true;
|
|
16736
|
-
this.authService.silentRefresh().then((success) => {
|
|
16737
|
-
this.isProcessing = false;
|
|
16738
|
-
if (success) {
|
|
16739
|
-
this.notifications(); // Changed: Establish SignalR connections after silent refresh
|
|
16740
|
-
this.router.navigate([this.redirectPath]);
|
|
16741
|
-
}
|
|
16742
|
-
// If refresh fails, stay on login page — user must re-enter credentials
|
|
16743
|
-
});
|
|
16744
|
-
return;
|
|
16745
|
-
}
|
|
16746
|
-
if (this.route.snapshot.queryParams["sso"] == "msal" && this.appConfig.microsoftAuth) {
|
|
16747
|
-
this.loginWithMS();
|
|
16748
|
-
}
|
|
16749
|
-
this.authService.autoLoginObserv.subscribe(x => {
|
|
16750
|
-
this.autoLogin = x;
|
|
16751
|
-
// console.log("AUTO LOGIN :" + this.autoLogin)
|
|
16752
|
-
});
|
|
16753
|
-
this.socialUser = null;
|
|
16754
|
-
this.authService.socialUserObserv.subscribe((socialUser) => {
|
|
16755
|
-
if (!socialUser)
|
|
16931
|
+
// Changed: Use shared tryRestoreSession — handles valid-session check and silent refresh in one call
|
|
16932
|
+
this.isProcessing = true;
|
|
16933
|
+
this.authService.tryRestoreSession().then(success => {
|
|
16934
|
+
this.isProcessing = false;
|
|
16935
|
+
if (success) {
|
|
16936
|
+
this.notifications();
|
|
16937
|
+
this.router.navigate([this.redirectPath]);
|
|
16756
16938
|
return;
|
|
16757
|
-
|
|
16758
|
-
this.
|
|
16759
|
-
this.user.password = socialUser.id; //dummy data
|
|
16760
|
-
this.user.token = socialUser.idToken;
|
|
16761
|
-
this.user.authType = socialUser.provider;
|
|
16762
|
-
this.login();
|
|
16939
|
+
}
|
|
16940
|
+
this.setupForm(); // Changed: Only set up login form if no session could be restored
|
|
16763
16941
|
});
|
|
16764
|
-
// this.authService.logoff();
|
|
16765
|
-
this.style = this.dataService.appConfig.loginStyle ?? 'default';
|
|
16766
16942
|
}
|
|
16767
16943
|
loginWithMS() {
|
|
16768
16944
|
this.msalService.loginPopup({
|
|
@@ -16792,6 +16968,25 @@ class LoginComponent {
|
|
|
16792
16968
|
recoverAccount() {
|
|
16793
16969
|
this.router.navigate(["recover-account"]);
|
|
16794
16970
|
}
|
|
16971
|
+
// Changed: Extracted from ngOnInit — runs only when no session could be restored
|
|
16972
|
+
setupForm() {
|
|
16973
|
+
if (this.route.snapshot.queryParams["sso"] == "msal" && this.appConfig.microsoftAuth) {
|
|
16974
|
+
this.loginWithMS();
|
|
16975
|
+
}
|
|
16976
|
+
this.authService.autoLoginObserv.subscribe(x => { this.autoLogin = x; });
|
|
16977
|
+
this.socialUser = null;
|
|
16978
|
+
this.authService.socialUserObserv.subscribe((socialUser) => {
|
|
16979
|
+
if (!socialUser)
|
|
16980
|
+
return;
|
|
16981
|
+
this.socialUser = socialUser;
|
|
16982
|
+
this.user.userName = socialUser.id;
|
|
16983
|
+
this.user.password = socialUser.id;
|
|
16984
|
+
this.user.token = socialUser.idToken;
|
|
16985
|
+
this.user.authType = socialUser.provider;
|
|
16986
|
+
this.login();
|
|
16987
|
+
});
|
|
16988
|
+
this.style = this.dataService.appConfig.loginStyle ?? 'default';
|
|
16989
|
+
}
|
|
16795
16990
|
login() {
|
|
16796
16991
|
if (this.user.userName == "" || this.user.password == "") {
|
|
16797
16992
|
this.messageService.toast("Please enter your credentials");
|
|
@@ -22501,6 +22696,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
22501
22696
|
}]
|
|
22502
22697
|
}] });
|
|
22503
22698
|
|
|
22699
|
+
// Config interfaces for the spa-landing component
|
|
22700
|
+
// Consumer apps provide this config to drive the entire landing page
|
|
22701
|
+
// Alsquare brand SVG — dark variant (for navbar on light backgrounds)
|
|
22702
|
+
const ALSQUARE_SVG_DARK = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
22703
|
+
<path d="M32 62C17.6 62 6 50.4 6 36S17.6 10 32 10c9.2 0 17.3 4.9 21.8 12.2" stroke="#1a3a7a" stroke-width="9" stroke-linecap="round" fill="none"/>
|
|
22704
|
+
<circle cx="32" cy="36" r="10" fill="white" stroke="#1a3a7a" stroke-width="7"/>
|
|
22705
|
+
<circle cx="32" cy="36" r="5.5" fill="#e8a020"/>
|
|
22706
|
+
<line x1="48" y1="22" x2="48" y2="62" stroke="#1a3a7a" stroke-width="9" stroke-linecap="round"/>
|
|
22707
|
+
<rect x="56" y="6" width="7" height="56" rx="3.5" fill="#e8a020"/>
|
|
22708
|
+
<rect x="67" y="0" width="6" height="6" rx="1" fill="#1a3a7a"/>
|
|
22709
|
+
<rect x="67" y="9" width="6" height="6" rx="1" fill="#1a3a7a"/>
|
|
22710
|
+
<rect x="75" y="0" width="5" height="5" rx="1" fill="#1a3a7a" opacity="0.7"/>
|
|
22711
|
+
<rect x="75" y="8" width="5" height="5" rx="1" fill="#1a3a7a" opacity="0.5"/>
|
|
22712
|
+
</svg>`;
|
|
22713
|
+
// Alsquare brand SVG — white variant (for footer on dark backgrounds)
|
|
22714
|
+
const ALSQUARE_SVG_WHITE = `<svg viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
22715
|
+
<path d="M32 62C17.6 62 6 50.4 6 36S17.6 10 32 10c9.2 0 17.3 4.9 21.8 12.2" stroke="#ffffff" stroke-width="9" stroke-linecap="round" fill="none"/>
|
|
22716
|
+
<circle cx="32" cy="36" r="10" fill="#0f172a" stroke="#ffffff" stroke-width="7"/>
|
|
22717
|
+
<circle cx="32" cy="36" r="5.5" fill="#e8a020"/>
|
|
22718
|
+
<line x1="48" y1="22" x2="48" y2="62" stroke="#ffffff" stroke-width="9" stroke-linecap="round"/>
|
|
22719
|
+
<rect x="56" y="6" width="7" height="56" rx="3.5" fill="#e8a020"/>
|
|
22720
|
+
<rect x="67" y="0" width="6" height="6" rx="1" fill="#ffffff"/>
|
|
22721
|
+
<rect x="67" y="9" width="6" height="6" rx="1" fill="#ffffff"/>
|
|
22722
|
+
<rect x="75" y="0" width="5" height="5" rx="1" fill="#ffffff" opacity="0.7"/>
|
|
22723
|
+
<rect x="75" y="8" width="5" height="5" rx="1" fill="#ffffff" opacity="0.5"/>
|
|
22724
|
+
</svg>`;
|
|
22725
|
+
|
|
22504
22726
|
/*
|
|
22505
22727
|
* Public API Surface of tin-spa
|
|
22506
22728
|
*/
|
|
@@ -22510,5 +22732,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.14", ngImpo
|
|
|
22510
22732
|
* Generated bundle index. Do not edit.
|
|
22511
22733
|
*/
|
|
22512
22734
|
|
|
22513
|
-
export { Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AlertComponent, AlertConfig, AlertMessage, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralService, GradesComponent, GroupsComponent, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryReceiptStatus, InventoryService, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, PageComponent, PageConfig, PayrollDashboardComponent, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchasingDashboardComponent, PushNotificationService, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaIndexModule, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TabsInternalComponent, TabsLiteComponent, TasksComponent, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, viewerDialog };
|
|
22735
|
+
export { ALSQUARE_SVG_DARK, ALSQUARE_SVG_WHITE, Account, AccountsComponent as AccountingAccountsComponent, AggregatesComponent as AccountingAggregatesComponent, AgingComponent as AccountingAgingComponent, CurrenciesComponent as AccountingCurrenciesComponent, AccountingDashboardComponent, InvoicesComponent as AccountingInvoicesComponent, ReportsComponent as AccountingReportsComponent, AccountingService, StatementComponent as AccountingStatementComponent, TransactionTypesComponent as AccountingTransactionTypesComponent, TransactionsComponent as AccountingTransactionsComponent, Action, ActivityComponent, AdminModule, AlertComponent, AlertConfig, AlertMessage, ApiResponse, AppConfig, AppModelsComponent, AssetStatus, AttachComponent, AuthService, BillingPageComponent, BrandsComponent, CacheConfig, CapItem, CapsulesComponent, CategoriesComponent, ChangePasswordComponent, ChangeUserPassword, ChartConfig, ChartsComponent, CheckComponent, ChipsComponent, Constants, Core, CreateAccountComponent, CustomersComponent, DataServiceLib, DateComponent, DatetimeComponent, DepartmentsComponent, DetailsDialog, DetailsDialogConfig, DetailsDialogProcessor, DetailsSource, DialogService, EmailComponent, EmployeesComponent, ExportService, FeatureDirective, FilterComponent, FormComponent, FormConfig, GeneralService, GradesComponent, GroupsComponent, HtmlComponent, HttpService, IndexModule, InventoryDashboardComponent, InventoryReceiptStatus, InventoryService, InvoiceDashboardComponent, InvoiceItemType, InvoiceStatus, LabelComponent, ListDialogComponent, ListDialogConfig, LoaderComponent, LoaderService, LoanPaymentsComponent, LoanProductsComponent, LoansComponent, LoansService, LogLevel, LogService, LoginComponent, LogsComponent, MembershipComponent, MessageService, MoneyComponent, MovementType, NavMenuComponent, NotesComponent, NotesConfig, NotificationsService, NumberComponent, OnboardingComponent, OptionComponent, OverviewDashboardComponent, PageComponent, PageConfig, PayrollDashboardComponent, PlansComponent, PositionsComponent, PreferencesComponent, PrivacyDialogComponent, Profile, ProfileComponent, PurchasingDashboardComponent, PushNotificationService, RecoverAccountComponent, Register, Role, RoleAccess, RolesComponent, SalesDashboardComponent, SearchComponent, SearchConfig, SecurityConfig, SelectBitwiseComponent, SelectComponent, SelectLiteComponent, SelectMultiComponent, SettingsComponent, SignupComponent, SignupData, SpaAdminModule, SpaIndexModule, SpaLandingComponent, SpaMatModule, SpaUserModule, StatusesComponent, Step, StepConfig, StepsComponent, StorageService, SubCategoriesComponent, SubscriptionPageComponent, SubscriptionService, SuppliersComponent, TabService, TableComponent, TableConfig, TabsComponent, TabsInternalComponent, TabsLiteComponent, TasksComponent, TenantsComponent, TermsDialogComponent, TextAreaComponent, TextComponent, TextMaskComponent, TextMultiComponent, TextSingleComponent, TileConfig, TilesComponent, TinSpaComponent, TinSpaModule, TinSpaService, TitleActionsComponent, TransactionTiming, UnitOfMeasure, UpdateService, User, UserModule, UsersComponent, ViewerComponent, WelcomeComponent, authGuard, dialogOptions, featureGuard, loginConfig, messageDialog, viewerDialog };
|
|
22514
22736
|
//# sourceMappingURL=tin-spa.mjs.map
|