@nauticana/sail 0.7.0 → 0.8.0
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/README.md +300 -1
- package/package.json +2 -2
- package/src/component/abstract/base_table.ts +19 -19
- package/src/component/abstract/base_view.ts +9 -9
- package/src/component/billing/checkout_button.html +1 -1
- package/src/component/billing/plan_selector.html +1 -1
- package/src/component/form/form_field.html +1 -1
- package/src/component/form/form_field.ts +7 -3
- package/src/component/form/form_table.html +4 -4
- package/src/component/form/form_table.ts +1 -1
- package/src/component/login/account_deletion.html +1 -1
- package/src/component/login/account_deletion.ts +4 -1
- package/src/component/login/chpass_component.html +1 -1
- package/src/component/login/confirm_chpass_component.html +1 -1
- package/src/component/login/confirm_register_component.html +1 -1
- package/src/component/login/login_component.html +1 -1
- package/src/component/login/otp_input.html +3 -3
- package/src/component/login/register_component.html +1 -1
- package/src/component/navigation/navigation.html +3 -3
- package/src/component/payment/user_payment_methods.html +46 -0
- package/src/component/payment/user_payment_methods.ts +10 -58
- package/src/component/payout/bank_info_form.html +59 -0
- package/src/component/payout/bank_info_form.ts +21 -77
- package/src/component/payout/provider_onboarding.html +70 -0
- package/src/component/payout/provider_onboarding.ts +9 -80
- package/src/component/security/trusted_devices.html +4 -4
- package/src/component/security/twofactor_setup.html +4 -4
- package/src/component/security/twofactor_verify.html +1 -1
- package/src/component/table/table_detail.html +9 -9
- package/src/component/table/table_detail.ts +10 -3
- package/src/component/table/table_edit.html +8 -8
- package/src/component/table/table_edit.ts +25 -14
- package/src/component/table/table_list.html +5 -5
- package/src/component/table/table_list.ts +17 -7
- package/src/component/table/table_lookup.html +4 -4
- package/src/component/table/table_lookup.ts +10 -9
- package/src/component/table/table_report.html +1 -1
- package/src/component/table/table_search.html +5 -5
- package/src/component/table/table_search.ts +30 -17
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<mat-label>Confirm your password</mat-label>
|
|
11
11
|
<input matInput type="password" formControlName="password" autocomplete="current-password" required>
|
|
12
12
|
</mat-form-field>
|
|
13
|
-
<button
|
|
13
|
+
<button matButton="elevated" class="primary" type="submit" [disabled]="!initForm.valid">Enable 2FA</button>
|
|
14
14
|
</form>
|
|
15
15
|
}
|
|
16
16
|
@case ('qr') {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
</div>
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
<button
|
|
35
|
+
<button matButton="elevated" class="primary" (click)="proceedToVerify()">I've saved my backup codes</button>
|
|
36
36
|
}
|
|
37
37
|
@case ('verify') {
|
|
38
38
|
<p class="auth-instructions">Enter the 6-digit code from your authenticator app to confirm setup.</p>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
<input matInput type="text" formControlName="code" maxlength="6" autocomplete="one-time-code" required>
|
|
43
43
|
</mat-form-field>
|
|
44
44
|
|
|
45
|
-
<button
|
|
45
|
+
<button matButton="elevated" class="primary" type="submit" [disabled]="!verifyForm.valid">
|
|
46
46
|
Verify and Enable
|
|
47
47
|
</button>
|
|
48
48
|
</form>
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
<mat-label>Current 6-digit code</mat-label>
|
|
60
60
|
<input matInput type="text" formControlName="code" maxlength="6" autocomplete="one-time-code" required>
|
|
61
61
|
</mat-form-field>
|
|
62
|
-
<button
|
|
62
|
+
<button matButton class="warn" type="submit" [disabled]="!disableForm.valid">Disable 2FA</button>
|
|
63
63
|
</form>
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<div class="auth-error" role="alert">{{ errorMessage() }}</div>
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
<button
|
|
34
|
+
<button matButton="elevated" class="primary" type="submit" [disabled]="!verifyForm.valid">
|
|
35
35
|
Verify
|
|
36
36
|
</button>
|
|
37
37
|
</form>
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
<div class="child-table-header">
|
|
3
3
|
<h3>{{ getCaption() }}</h3>
|
|
4
4
|
@if (canCreate()) {
|
|
5
|
-
<button
|
|
5
|
+
<button matButton="elevated" class="accent" (click)="addRecord()">New Record</button>
|
|
6
6
|
}
|
|
7
7
|
@for (action of getActions(false); track action.action) {
|
|
8
8
|
@if (canExecuteAction(action)) {
|
|
9
|
-
<button
|
|
9
|
+
<button matButton="outlined" type="button"
|
|
10
10
|
[title]="action.caption"
|
|
11
11
|
(click)="executeAction(action)">
|
|
12
12
|
@if (action.icon) { <mat-icon>{{ action.icon }}</mat-icon> }
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
@if (isReadOnly(col)) {
|
|
34
34
|
{{ record[col] }}
|
|
35
35
|
} @else {
|
|
36
|
-
<dynamic-field [tableName]="tableName" [value]="record[col]" [field]="col" (valueChange)="record[col]=$event" subscriptSizing="dynamic"></dynamic-field>
|
|
36
|
+
<dynamic-field [tableName]="tableName()" [value]="record[col]" [field]="col" (valueChange)="record[col]=$event" subscriptSizing="dynamic"></dynamic-field>
|
|
37
37
|
}
|
|
38
38
|
</td>
|
|
39
39
|
}
|
|
40
40
|
<td>
|
|
41
|
-
<button
|
|
42
|
-
<button
|
|
41
|
+
<button matIconButton (click)="saveRow()" title="Save"><mat-icon>save</mat-icon></button>
|
|
42
|
+
<button matIconButton (click)="cancelRow(record)" title="Cancel"><mat-icon>cancel</mat-icon></button>
|
|
43
43
|
</td>
|
|
44
44
|
} @else {
|
|
45
45
|
@for (col of displayedColumns; track col) {
|
|
@@ -47,17 +47,17 @@
|
|
|
47
47
|
}
|
|
48
48
|
<td>
|
|
49
49
|
@if (canUpdate()) {
|
|
50
|
-
<button
|
|
50
|
+
<button matIconButton (click)="editRow(record)" title="Edit"><mat-icon>edit</mat-icon></button>
|
|
51
51
|
}
|
|
52
52
|
@if (record[config.opField] === 'D') {
|
|
53
|
-
<button
|
|
53
|
+
<button matIconButton (click)="unDeleteRow(record)" title="Undelete"><mat-icon>restore_from_trash</mat-icon></button>
|
|
54
54
|
} @else if (canDelete()) {
|
|
55
|
-
<button
|
|
55
|
+
<button matIconButton (click)="deleteRow(record)" title="Delete"><mat-icon>delete</mat-icon></button>
|
|
56
56
|
}
|
|
57
57
|
@if (record[config.opField] !== 'I') {
|
|
58
58
|
@for (action of getActions(true); track action.action) {
|
|
59
59
|
@if (canExecuteAction(action)) {
|
|
60
|
-
<button
|
|
60
|
+
<button matIconButton type="button"
|
|
61
61
|
[title]="action.caption"
|
|
62
62
|
(click)="executeAction(action, record)">
|
|
63
63
|
<mat-icon>{{ action.icon || 'play_arrow' }}</mat-icon>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component,
|
|
1
|
+
import { ChangeDetectionStrategy, Component, effect, inject, input, OnInit, ViewEncapsulation } from "@angular/core";
|
|
2
2
|
import { DynamicField } from "../form/form_field";
|
|
3
3
|
import { MatButtonModule } from "@angular/material/button";
|
|
4
4
|
import { BaseForm } from "../abstract/base_form";
|
|
@@ -20,13 +20,14 @@ import { TableAction } from "../../model/appdata";
|
|
|
20
20
|
],
|
|
21
21
|
})
|
|
22
22
|
export class TableDetail extends BaseForm implements OnInit {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
readonly tableNameInput = input('', { alias: 'tableName' });
|
|
24
|
+
readonly recordsInput = input<any[]>([], { alias: 'records' });
|
|
25
25
|
readonly parentTableName = input('');
|
|
26
26
|
readonly parentRecord = input<any>({});
|
|
27
27
|
/** When true, the parent view is in read-only mode — disable all child row editing. */
|
|
28
28
|
readonly parentReadOnly = input(false);
|
|
29
29
|
|
|
30
|
+
records: any[] = [];
|
|
30
31
|
editingRecord: any = null;
|
|
31
32
|
originalRecord: any = null;
|
|
32
33
|
displayedColumns: string[] = [];
|
|
@@ -35,6 +36,12 @@ export class TableDetail extends BaseForm implements OnInit {
|
|
|
35
36
|
private readonly dialog = inject(MatDialog);
|
|
36
37
|
private readonly backendService = inject(BackendService);
|
|
37
38
|
|
|
39
|
+
constructor() {
|
|
40
|
+
super();
|
|
41
|
+
effect(() => { const v = this.tableNameInput(); if (v) this.tableName.set(v); });
|
|
42
|
+
effect(() => { this.records = this.recordsInput(); });
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
/**
|
|
39
46
|
* Fire a TableAction. Per-row actions require the record to be
|
|
40
47
|
* server-side (not 'I' inserted-locally-not-saved); table-level
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
}
|
|
10
10
|
</span>
|
|
11
11
|
@if (isDialog) {
|
|
12
|
-
<button
|
|
12
|
+
<button matIconButton type="button" class="table-edit-close"
|
|
13
13
|
(click)="onCancel()" aria-label="Close dialog">
|
|
14
14
|
<mat-icon>close</mat-icon>
|
|
15
15
|
</button>
|
|
@@ -20,21 +20,21 @@
|
|
|
20
20
|
<div class="actions-bar">
|
|
21
21
|
@if (isReadOnlyMode) {
|
|
22
22
|
@if (canUpdate()) {
|
|
23
|
-
<button
|
|
23
|
+
<button matButton="elevated" class="primary" (click)="onEdit()">Edit</button>
|
|
24
24
|
}
|
|
25
25
|
@if (canCreate()) {
|
|
26
|
-
<button
|
|
26
|
+
<button matButton="elevated" class="accent" (click)="onNew()">New</button>
|
|
27
27
|
}
|
|
28
28
|
@if (canDelete()) {
|
|
29
|
-
<button
|
|
29
|
+
<button matButton class="warn" (click)="onDelete()">Delete</button>
|
|
30
30
|
}
|
|
31
31
|
} @else {
|
|
32
|
-
<button
|
|
33
|
-
<button
|
|
32
|
+
<button matButton="elevated" class="primary" (click)="onSave()">Save</button>
|
|
33
|
+
<button matButton (click)="onCancel()">Cancel</button>
|
|
34
34
|
}
|
|
35
35
|
</div>
|
|
36
36
|
|
|
37
|
-
<record-form [tableName]="tableName" [record]="editableRecord" [isReadOnly]="isReadOnlyBound"></record-form>
|
|
37
|
+
<record-form [tableName]="tableName()" [record]="editableRecord" [isReadOnly]="isReadOnlyBound"></record-form>
|
|
38
38
|
|
|
39
39
|
@if (tabCount > 0) {
|
|
40
40
|
<mat-tab-group>
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<table-detail
|
|
46
46
|
[records]="editableRecord[key]"
|
|
47
47
|
[tableName]="getTableName(key)"
|
|
48
|
-
[parentTableName]="tableName"
|
|
48
|
+
[parentTableName]="tableName()"
|
|
49
49
|
[parentRecord]="editableRecord"
|
|
50
50
|
[parentReadOnly]="isReadOnlyMode">
|
|
51
51
|
</table-detail>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject,
|
|
1
|
+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, inject, input, OnInit, signal, ViewEncapsulation } from "@angular/core";
|
|
2
2
|
import { BaseForm } from "../abstract/base_form";
|
|
3
3
|
import { MAT_DIALOG_DATA, MatDialogRef } from "@angular/material/dialog";
|
|
4
4
|
import { MatTabsModule } from "@angular/material/tabs";
|
|
@@ -22,9 +22,11 @@ import { MatIconModule } from "@angular/material/icon";
|
|
|
22
22
|
]
|
|
23
23
|
})
|
|
24
24
|
export class TableEdit extends BaseForm implements OnInit {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
readonly recordInput = input<any>({}, { alias: 'record' });
|
|
26
|
+
readonly tableNameInput = input('', { alias: 'tableName' });
|
|
27
|
+
readonly apiNameInput = input('', { alias: 'apiName' });
|
|
28
|
+
readonly record = signal<any>({});
|
|
29
|
+
readonly apiName = signal('');
|
|
28
30
|
isReadOnlyMode = true;
|
|
29
31
|
private readonly backendService = inject(BackendService);
|
|
30
32
|
private readonly dialogRef = inject(MatDialogRef<TableEdit>, {optional: true});
|
|
@@ -37,11 +39,18 @@ export class TableEdit extends BaseForm implements OnInit {
|
|
|
37
39
|
/** Snapshot taken when the user enters edit mode; used for dirty-tracking on save. */
|
|
38
40
|
private originalSnapshot: any = null;
|
|
39
41
|
|
|
42
|
+
constructor() {
|
|
43
|
+
super();
|
|
44
|
+
effect(() => { const v = this.recordInput(); if (v) this.record.set(v); });
|
|
45
|
+
effect(() => { const v = this.tableNameInput(); if (v) this.tableName.set(v); });
|
|
46
|
+
effect(() => { const v = this.apiNameInput(); if (v) this.apiName.set(v); });
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
ngOnInit(): void {
|
|
41
50
|
if (this.dialogData) {
|
|
42
|
-
this.record
|
|
43
|
-
this.tableName
|
|
44
|
-
this.apiName
|
|
51
|
+
this.record.set(this.dialogData.record ?? {});
|
|
52
|
+
this.tableName.set(this.dialogData.tableName ?? '');
|
|
53
|
+
this.apiName.set(this.dialogData.apiName ?? '');
|
|
45
54
|
this.isNew = this.dialogData.isNew ?? false;
|
|
46
55
|
this.isReadOnlyMode = !this.isNew;
|
|
47
56
|
}
|
|
@@ -55,7 +64,8 @@ export class TableEdit extends BaseForm implements OnInit {
|
|
|
55
64
|
|
|
56
65
|
private initializeNewRecord() {
|
|
57
66
|
this.editableRecord = this.emptyRecord();
|
|
58
|
-
|
|
67
|
+
const rec = this.record();
|
|
68
|
+
if (rec) Object.assign(this.editableRecord, rec);
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
private buildTabs() {
|
|
@@ -70,16 +80,17 @@ export class TableEdit extends BaseForm implements OnInit {
|
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
private fetchFullRecord() {
|
|
73
|
-
|
|
83
|
+
const rec = this.record();
|
|
84
|
+
this.editableRecord = {...rec};
|
|
74
85
|
this.formatRecordTimeStamp(this.editableRecord);
|
|
75
86
|
|
|
76
|
-
const filter = this.getKeyFilters(
|
|
87
|
+
const filter = this.getKeyFilters(rec);
|
|
77
88
|
if (!filter) {
|
|
78
89
|
console.warn('Key definition could not be found');
|
|
79
90
|
return;
|
|
80
91
|
}
|
|
81
92
|
|
|
82
|
-
this.backendService.get<any>(this.apiName, filter).subscribe({
|
|
93
|
+
this.backendService.get<any>(this.apiName(), filter).subscribe({
|
|
83
94
|
next: (record) => {
|
|
84
95
|
if (!record) {
|
|
85
96
|
console.error('failed to read single record from the backend');
|
|
@@ -123,7 +134,7 @@ export class TableEdit extends BaseForm implements OnInit {
|
|
|
123
134
|
if (this.dialogRef) {
|
|
124
135
|
this.dialogRef.close(result);
|
|
125
136
|
} else {
|
|
126
|
-
this.backendService.post(this.apiName, result).subscribe({
|
|
137
|
+
this.backendService.post(this.apiName(), result).subscribe({
|
|
127
138
|
next: () => {
|
|
128
139
|
this.isReadOnlyMode = true;
|
|
129
140
|
this.isNew = false;
|
|
@@ -139,7 +150,7 @@ export class TableEdit extends BaseForm implements OnInit {
|
|
|
139
150
|
if (confirm('Are you sure you want to delete this record?')) {
|
|
140
151
|
const result = {...this.editableRecord};
|
|
141
152
|
result[this.config.opField] = 'D';
|
|
142
|
-
this.backendService.post(this.apiName, result).subscribe({
|
|
153
|
+
this.backendService.post(this.apiName(), result).subscribe({
|
|
143
154
|
next: () => {
|
|
144
155
|
this.isReadOnlyMode = true;
|
|
145
156
|
this.isReadOnlyBound = this.isReadOnly.bind(this);
|
|
@@ -156,7 +167,7 @@ export class TableEdit extends BaseForm implements OnInit {
|
|
|
156
167
|
return;
|
|
157
168
|
}
|
|
158
169
|
this.isReadOnlyMode = true;
|
|
159
|
-
this.editableRecord = {...this.record};
|
|
170
|
+
this.editableRecord = {...this.record()};
|
|
160
171
|
this.isNew = false;
|
|
161
172
|
this.isReadOnlyBound = this.isReadOnly.bind(this);
|
|
162
173
|
}
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
<div class="table-list__toolbar">
|
|
4
4
|
@if (canCreate()) {
|
|
5
|
-
<button
|
|
5
|
+
<button matButton="elevated" class="accent" (click)="addRecord()">New Record</button>
|
|
6
6
|
}
|
|
7
7
|
@for (action of getActions(false); track action.action) {
|
|
8
8
|
@if (canExecuteAction(action)) {
|
|
9
|
-
<button
|
|
9
|
+
<button matButton="outlined" type="button"
|
|
10
10
|
[title]="action.caption"
|
|
11
11
|
(click)="executeAction(action)">
|
|
12
12
|
@if (action.icon) { <mat-icon>{{ action.icon }}</mat-icon> }
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
} @else if (column !== 'actions') {
|
|
38
38
|
{{ displayValue(column, record[column]) }}
|
|
39
39
|
} @else {
|
|
40
|
-
<button
|
|
41
|
-
<button
|
|
40
|
+
<button matIconButton type="button" (click)="editRecord(record)" title="Edit"><mat-icon>edit</mat-icon></button>
|
|
41
|
+
<button matIconButton type="button" (click)="deleteRecord(record)" title="Delete"><mat-icon>delete</mat-icon></button>
|
|
42
42
|
@for (action of getActions(true); track action.action) {
|
|
43
43
|
@if (canExecuteAction(action)) {
|
|
44
|
-
<button
|
|
44
|
+
<button matIconButton type="button"
|
|
45
45
|
[title]="action.caption"
|
|
46
46
|
(click)="executeAction(action, record)">
|
|
47
47
|
<mat-icon>{{ action.icon || 'play_arrow' }}</mat-icon>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit, ViewEncapsulation } from "@angular/core";
|
|
1
|
+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, effect, inject, input, OnInit, ViewEncapsulation } from "@angular/core";
|
|
2
2
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
|
3
3
|
import { ActivatedRoute, Params } from "@angular/router";
|
|
4
4
|
import { BaseView } from "../abstract/base_view";
|
|
@@ -18,17 +18,27 @@ import { MatIconModule } from "@angular/material/icon";
|
|
|
18
18
|
})
|
|
19
19
|
export class TableList extends BaseView implements OnInit {
|
|
20
20
|
override dialogWidth = '400px';
|
|
21
|
+
readonly tableNameInput = input('', { alias: 'tableName' });
|
|
22
|
+
readonly apiNameInput = input('', { alias: 'apiName' });
|
|
23
|
+
readonly dialogComponentInput = input<any>(undefined, { alias: 'dialogComponent' });
|
|
21
24
|
|
|
22
25
|
private readonly route = inject(ActivatedRoute);
|
|
23
26
|
private readonly backendService = inject(BackendService);
|
|
24
27
|
private readonly cdr = inject(ChangeDetectorRef);
|
|
25
28
|
private readonly destroyRef = inject(DestroyRef);
|
|
26
29
|
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
effect(() => { const v = this.tableNameInput(); if (v) this.tableName.set(v); });
|
|
33
|
+
effect(() => { const v = this.apiNameInput(); if (v) this.apiName.set(v); });
|
|
34
|
+
effect(() => { const v = this.dialogComponentInput();if (v) this.dialogComponent.set(v); });
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
ngOnInit() {
|
|
28
38
|
const data = this.route.snapshot.data;
|
|
29
|
-
if (!this.tableName && data['tableName']) this.tableName
|
|
30
|
-
if (!this.apiName && data['apiName']) this.apiName
|
|
31
|
-
if (!this.dialogComponent) this.dialogComponent
|
|
39
|
+
if (!this.tableName() && data['tableName']) this.tableName.set(data['tableName']);
|
|
40
|
+
if (!this.apiName() && data['apiName']) this.apiName.set(data['apiName']);
|
|
41
|
+
if (!this.dialogComponent()) this.dialogComponent.set(data['dialogComponent'] ?? TableEdit);
|
|
32
42
|
this.route.queryParams.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => this.handleQueryParams(params));
|
|
33
43
|
}
|
|
34
44
|
|
|
@@ -39,7 +49,7 @@ export class TableList extends BaseView implements OnInit {
|
|
|
39
49
|
}
|
|
40
50
|
if (confirm('Are you sure you want to delete this record?')) {
|
|
41
51
|
const keyFilter = this.getKeyFilters(record);
|
|
42
|
-
this.backendService.delete(this.apiName, keyFilter).subscribe({
|
|
52
|
+
this.backendService.delete(this.apiName(), keyFilter).subscribe({
|
|
43
53
|
next: () => this.fetchRecords(),
|
|
44
54
|
error: (err) => console.error('Delete failed', err),
|
|
45
55
|
});
|
|
@@ -56,7 +66,7 @@ export class TableList extends BaseView implements OnInit {
|
|
|
56
66
|
return;
|
|
57
67
|
}
|
|
58
68
|
if (confirm('Are you sure you want to save this record?')) {
|
|
59
|
-
this.backendService.post(this.apiName, result).subscribe({
|
|
69
|
+
this.backendService.post(this.apiName(), result).subscribe({
|
|
60
70
|
next: () => this.fetchRecords(),
|
|
61
71
|
error: (err) => console.error('Save failed', err),
|
|
62
72
|
});
|
|
@@ -68,7 +78,7 @@ export class TableList extends BaseView implements OnInit {
|
|
|
68
78
|
alert('Missing authorization to read records');
|
|
69
79
|
return;
|
|
70
80
|
}
|
|
71
|
-
this.backendService.list<{[key: string]: any}>(this.apiName, this.searchTerms).subscribe({
|
|
81
|
+
this.backendService.list<{[key: string]: any}>(this.apiName(), this.searchTerms).subscribe({
|
|
72
82
|
next: (records) => {
|
|
73
83
|
this.records = records;
|
|
74
84
|
this.updateDisplayedColumns(records);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<h2 mat-dialog-title>{{ getCaption() }} Search</h2>
|
|
2
2
|
<div mat-dialog-content>
|
|
3
|
-
<record-form [tableName]="tableName" [record]="searchRecord" [fields]="searchColumns" [isReadOnly]="isReadOnlyBound"></record-form>
|
|
3
|
+
<record-form [tableName]="tableName()" [record]="searchRecord" [fields]="searchColumns" [isReadOnly]="isReadOnlyBound"></record-form>
|
|
4
4
|
<div class="search-actions">
|
|
5
|
-
<button
|
|
6
|
-
<button
|
|
5
|
+
<button matButton="elevated" class="primary" (click)="onSearch()">Search</button>
|
|
6
|
+
<button matButton (click)="onClear()">Clear</button>
|
|
7
7
|
</div>
|
|
8
8
|
@if (records.length > 0) {
|
|
9
9
|
<table>
|
|
@@ -31,5 +31,5 @@
|
|
|
31
31
|
}
|
|
32
32
|
</div>
|
|
33
33
|
<div mat-dialog-actions>
|
|
34
|
-
<button
|
|
34
|
+
<button matButton [mat-dialog-close]>Cancel</button>
|
|
35
35
|
</div>
|
|
@@ -22,7 +22,6 @@ import { BackendService } from "../../service/rest_service";
|
|
|
22
22
|
],
|
|
23
23
|
})
|
|
24
24
|
export class TableLookup extends BaseView implements OnInit {
|
|
25
|
-
override apiName = '';
|
|
26
25
|
searchRecord: any = {};
|
|
27
26
|
searchColumns: string[] = [];
|
|
28
27
|
|
|
@@ -33,8 +32,8 @@ export class TableLookup extends BaseView implements OnInit {
|
|
|
33
32
|
|
|
34
33
|
ngOnInit() {
|
|
35
34
|
if (this.data) {
|
|
36
|
-
this.tableName
|
|
37
|
-
this.apiName
|
|
35
|
+
this.tableName.set(this.data.tableName ?? '');
|
|
36
|
+
this.apiName.set(this.data.apiName ?? '');
|
|
38
37
|
}
|
|
39
38
|
this.searchColumns = this.getDisplayedColumns();
|
|
40
39
|
this.searchRecord = this.emptyRecord();
|
|
@@ -42,13 +41,14 @@ export class TableLookup extends BaseView implements OnInit {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
private resolveApi() {
|
|
45
|
-
|
|
44
|
+
const tableName = this.tableName();
|
|
45
|
+
if (!this.apiName() && tableName) {
|
|
46
46
|
this.cacheService.getAppData().subscribe((data) => {
|
|
47
47
|
if (data.Apis) {
|
|
48
48
|
for (const api in data.Apis) {
|
|
49
49
|
const entry = data.Apis[api];
|
|
50
|
-
if (entry.Table.TableName ===
|
|
51
|
-
this.apiName
|
|
50
|
+
if (entry.Table.TableName === tableName) {
|
|
51
|
+
this.apiName.set(entry.Version ? entry.Version + '/' + api : api);
|
|
52
52
|
break;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -59,11 +59,12 @@ export class TableLookup extends BaseView implements OnInit {
|
|
|
59
59
|
|
|
60
60
|
onSearch() {
|
|
61
61
|
this.resolveApi();
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
const apiName = this.apiName();
|
|
63
|
+
if (!apiName) {
|
|
64
|
+
console.error('API name not found for table ' + this.tableName());
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
|
-
this.backendService.list<{[key: string]: any}>(
|
|
67
|
+
this.backendService.list<{[key: string]: any}>(apiName, this.buildSearchTerms(this.searchRecord)).subscribe({
|
|
67
68
|
next: (records) => {
|
|
68
69
|
this.records = records;
|
|
69
70
|
this.updateDisplayedColumns(records);
|
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
}
|
|
7
7
|
</h1>
|
|
8
8
|
<div class="page-container">
|
|
9
|
-
<record-form [tableName]="tableName" [record]="editableRecord" [fields]="searchColumns" [isReadOnly]="isReadOnlyBound"></record-form>
|
|
9
|
+
<record-form [tableName]="tableName()" [record]="editableRecord" [fields]="searchColumns" [isReadOnly]="isReadOnlyBound"></record-form>
|
|
10
10
|
</div>
|
|
11
11
|
<div class="search-actions">
|
|
12
|
-
<button
|
|
13
|
-
<button
|
|
12
|
+
<button matButton="elevated" class="primary" (click)="onSearch()">Search</button>
|
|
13
|
+
<button matButton (click)="onClear()">Clear</button>
|
|
14
14
|
@if (canCreate()) {
|
|
15
|
-
<button
|
|
15
|
+
<button matButton="elevated" class="accent" (click)="onAddRecord()">New Record</button>
|
|
16
16
|
}
|
|
17
17
|
@for (action of getActions(false); track action.action) {
|
|
18
18
|
@if (canExecuteAction(action)) {
|
|
19
|
-
<button
|
|
19
|
+
<button matButton="outlined" type="button"
|
|
20
20
|
[title]="action.caption"
|
|
21
21
|
(click)="executeAction(action)">
|
|
22
22
|
@if (action.icon) { <mat-icon>{{ action.icon }}</mat-icon> }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject,
|
|
1
|
+
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, inject, input, OnInit, signal, ViewEncapsulation } from "@angular/core";
|
|
2
2
|
import { MatButtonModule } from "@angular/material/button";
|
|
3
3
|
import { MatIconModule } from "@angular/material/icon";
|
|
4
4
|
import { ActivatedRoute, Router } from "@angular/router";
|
|
@@ -20,11 +20,20 @@ import { BackendService } from "../../service/rest_service";
|
|
|
20
20
|
],
|
|
21
21
|
})
|
|
22
22
|
export class TableSearch extends BaseForm implements OnInit {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
readonly tableNameInput = input('', { alias: 'tableName' });
|
|
24
|
+
readonly apiNameInput = input('', { alias: 'apiName' });
|
|
25
|
+
readonly targetRouteInput = input('', { alias: 'targetRoute' });
|
|
26
|
+
readonly apiName = signal('');
|
|
27
|
+
readonly targetRoute = signal('');
|
|
26
28
|
searchColumns: string[] = [];
|
|
27
29
|
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
effect(() => { const v = this.tableNameInput(); if (v) this.tableName.set(v); });
|
|
33
|
+
effect(() => { const v = this.apiNameInput(); if (v) this.apiName.set(v); });
|
|
34
|
+
effect(() => { const v = this.targetRouteInput();if (v) this.targetRoute.set(v); });
|
|
35
|
+
}
|
|
36
|
+
|
|
28
37
|
private readonly router = inject(Router);
|
|
29
38
|
private readonly route = inject(ActivatedRoute);
|
|
30
39
|
private readonly cdr = inject(ChangeDetectorRef);
|
|
@@ -52,21 +61,23 @@ export class TableSearch extends BaseForm implements OnInit {
|
|
|
52
61
|
|
|
53
62
|
ngOnInit() {
|
|
54
63
|
const data = this.route.snapshot.data;
|
|
55
|
-
if (!this.tableName && data['tableName']) this.tableName
|
|
56
|
-
if (!this.apiName && data['apiName']) this.apiName
|
|
57
|
-
if (!this.targetRoute && data['targetRoute']) this.targetRoute
|
|
64
|
+
if (!this.tableName() && data['tableName']) this.tableName.set(data['tableName']);
|
|
65
|
+
if (!this.apiName() && data['apiName']) this.apiName.set(data['apiName']);
|
|
66
|
+
if (!this.targetRoute() && data['targetRoute']) this.targetRoute.set(data['targetRoute']);
|
|
58
67
|
this.title = this.getCaption() + ' Search';
|
|
59
|
-
if (!this.targetRoute && (this.apiName || this.tableName)) {
|
|
68
|
+
if (!this.targetRoute() && (this.apiName() || this.tableName())) {
|
|
60
69
|
this.cacheService.getMenus().subscribe((menus: ApplicationMenu[]) => {
|
|
70
|
+
const apiName = this.apiName();
|
|
71
|
+
const tableName = this.tableName();
|
|
61
72
|
for (const menu of menus) {
|
|
62
73
|
for (const page of menu.ApplicationMenuItems!) {
|
|
63
|
-
if (
|
|
64
|
-
(
|
|
74
|
+
if (apiName.endsWith(page.RestUri!) ||
|
|
75
|
+
(tableName && page.RestUri!.includes(tableName))
|
|
65
76
|
) {
|
|
66
77
|
if (page.FilterOnList) {
|
|
67
|
-
this.targetRoute
|
|
78
|
+
this.targetRoute.set('/' + menu.Id!.toLowerCase() + '/' + page.ItemId!);
|
|
68
79
|
} else {
|
|
69
|
-
this.targetRoute
|
|
80
|
+
this.targetRoute.set('/' + menu.Id!.toLowerCase() + '/' + page.ItemId! + '/list');
|
|
70
81
|
}
|
|
71
82
|
this.cdr.markForCheck();
|
|
72
83
|
return;
|
|
@@ -80,11 +91,12 @@ export class TableSearch extends BaseForm implements OnInit {
|
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
onSearch() {
|
|
83
|
-
|
|
94
|
+
const targetRoute = this.targetRoute();
|
|
95
|
+
if (!targetRoute) {
|
|
84
96
|
alert('Navigation target not found for this table');
|
|
85
97
|
return;
|
|
86
98
|
}
|
|
87
|
-
this.router.navigate([
|
|
99
|
+
this.router.navigate([targetRoute], {queryParams: this.buildSearchTerms(this.editableRecord)});
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
onClear() {
|
|
@@ -106,7 +118,7 @@ export class TableSearch extends BaseForm implements OnInit {
|
|
|
106
118
|
*/
|
|
107
119
|
private emptySearchRecord(): any {
|
|
108
120
|
const rec = this.emptyRecord();
|
|
109
|
-
const tableDef = this.cacheService.getTableDefinition(this.tableName);
|
|
121
|
+
const tableDef = this.cacheService.getTableDefinition(this.tableName());
|
|
110
122
|
if (tableDef?.Columns) {
|
|
111
123
|
for (const col of tableDef.Columns) {
|
|
112
124
|
if (col.DataType === 'boolean') {
|
|
@@ -122,10 +134,11 @@ export class TableSearch extends BaseForm implements OnInit {
|
|
|
122
134
|
alert('Missing authorization to create records');
|
|
123
135
|
return;
|
|
124
136
|
}
|
|
125
|
-
|
|
137
|
+
const targetRoute = this.targetRoute();
|
|
138
|
+
if (!targetRoute) {
|
|
126
139
|
alert('Navigation target not found for this table');
|
|
127
140
|
return;
|
|
128
141
|
}
|
|
129
|
-
this.router.navigate([
|
|
142
|
+
this.router.navigate([targetRoute], {queryParams: {'_action': 'create'}});
|
|
130
143
|
}
|
|
131
144
|
}
|