@ferhaps/easy-ui-lib 0.0.7 → 1.0.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 +41 -22
- package/ng-package.json +7 -0
- package/package.json +26 -44
- package/src/lib/components/default-dialog/default-dialog.component.html +19 -0
- package/src/lib/components/default-dialog/default-dialog.component.scss +41 -0
- package/src/lib/components/default-dialog/default-dialog.component.ts +23 -0
- package/src/lib/components/error-dispaly.component.ts +48 -0
- package/src/lib/components/error-handler/error-handler.component.html +0 -0
- package/src/lib/components/error-handler/error-handler.component.scss +0 -0
- package/src/lib/components/error-handler/error-handler.component.ts +44 -0
- package/src/lib/components/error-handler/error-popup/error-popup.component.html +13 -0
- package/src/lib/components/error-handler/error-popup/error-popup.component.scss +19 -0
- package/src/lib/components/error-handler/error-popup/error-popup.component.ts +21 -0
- package/src/lib/components/global-loader/global-loader.component.html +5 -0
- package/src/lib/components/global-loader/global-loader.component.scss +12 -0
- package/src/lib/components/global-loader/global-loader.component.ts +17 -0
- package/src/lib/components/search-bar.component.ts +70 -0
- package/src/lib/components/table/table.component.html +121 -0
- package/src/lib/components/table/table.component.scss +116 -0
- package/src/lib/components/table/table.component.ts +105 -0
- package/src/lib/components/table-sort-header/table-sort-header.component.html +7 -0
- package/src/lib/components/table-sort-header/table-sort-header.component.scss +17 -0
- package/src/lib/components/table-sort-header/table-sort-header.component.ts +31 -0
- package/src/lib/directives/fields-match-validator.directive.ts +35 -0
- package/src/lib/directives/password-validator.directive.ts +26 -0
- package/src/lib/directives/phone-validation.directive.ts +24 -0
- package/src/lib/pipes/blank-filler.pipe.ts +13 -0
- package/src/lib/pipes/snake-case-parser.pipe.ts +17 -0
- package/src/lib/services/error.service.ts +15 -0
- package/src/lib/services/loader.service.ts +14 -0
- package/src/lib/utils/animations.ts +29 -0
- package/{lib/utils/types.d.ts → src/lib/utils/types.ts} +3 -2
- package/src/lib/utils/utils.ts +65 -0
- package/{public-api.d.ts → src/public-api.ts} +20 -17
- package/tsconfig.lib.json +14 -0
- package/tsconfig.lib.prod.json +10 -0
- package/tsconfig.spec.json +14 -0
- package/fesm2022/ferhaps-easy-ui-lib.mjs +0 -862
- package/fesm2022/ferhaps-easy-ui-lib.mjs.map +0 -1
- package/index.d.ts +0 -5
- package/lib/components/chip/chip.component.d.ts +0 -16
- package/lib/components/default-dialog/default-dialog.component.d.ts +0 -19
- package/lib/components/error-dispaly.component.d.ts +0 -17
- package/lib/components/error-handler/error-handler.component.d.ts +0 -18
- package/lib/components/error-handler/error-popup/error-popup.component.d.ts +0 -17
- package/lib/components/global-loader/global-loader.component.d.ts +0 -14
- package/lib/components/search-bar.component.d.ts +0 -18
- package/lib/components/table/table.component.d.ts +0 -58
- package/lib/components/table-sort-header/table-sort-header.component.d.ts +0 -17
- package/lib/directives/fields-match-validator.directive.d.ts +0 -16
- package/lib/directives/password-validator.directive.d.ts +0 -13
- package/lib/directives/phone-validation.directive.d.ts +0 -13
- package/lib/pipes/blank-filler.pipe.d.ts +0 -13
- package/lib/pipes/snake-case-parser.pipe.d.ts +0 -13
- package/lib/services/error.service.d.ts +0 -9
- package/lib/services/loader.service.d.ts +0 -8
- package/lib/utils/animations.d.ts +0 -3
- package/lib/utils/utils.d.ts +0 -1
package/README.md
CHANGED
|
@@ -17,17 +17,6 @@ This library requires:
|
|
|
17
17
|
|
|
18
18
|
## Components
|
|
19
19
|
|
|
20
|
-
### ChipComponent
|
|
21
|
-
A selectable chip component for displaying tags or filters.
|
|
22
|
-
```typescript
|
|
23
|
-
<lib-chip
|
|
24
|
-
[value]="value"
|
|
25
|
-
[isSelected]="isSelected"
|
|
26
|
-
[text]="displayText"
|
|
27
|
-
(selected)="onChipSelected($event)">
|
|
28
|
-
</lib-chip>
|
|
29
|
-
```
|
|
30
|
-
|
|
31
20
|
### TableComponent
|
|
32
21
|
A feature-rich table component supporting:
|
|
33
22
|
- Sorting
|
|
@@ -51,6 +40,7 @@ A feature-rich table component supporting:
|
|
|
51
40
|
draggable: true
|
|
52
41
|
}"
|
|
53
42
|
(action)="handleTableAction($event)">
|
|
43
|
+
<div class="upper-part">Optional content to be displyed at the top of the table</div>
|
|
54
44
|
</lib-table>
|
|
55
45
|
```
|
|
56
46
|
|
|
@@ -80,13 +70,26 @@ A customizable dialog component with optional back button.
|
|
|
80
70
|
### GlobalLoaderComponent
|
|
81
71
|
A centered spinner overlay for loading states.
|
|
82
72
|
```typescript
|
|
83
|
-
<lib-global-loader
|
|
73
|
+
<lib-global-loader />
|
|
74
|
+
|
|
75
|
+
private loaderService = inject(LoaderService);
|
|
76
|
+
this.loaderService.setLoading(true);
|
|
77
|
+
// do stuff
|
|
78
|
+
this.loaderService.setLoading(false);
|
|
84
79
|
```
|
|
85
80
|
|
|
86
81
|
### ErrorHandlerComponent
|
|
87
82
|
Displays error messages in a dialog format.
|
|
88
83
|
```typescript
|
|
89
|
-
<lib-error-handler
|
|
84
|
+
<lib-error-handler />
|
|
85
|
+
|
|
86
|
+
private errorService = inject(ErrorService);
|
|
87
|
+
try {
|
|
88
|
+
this.apiCall();
|
|
89
|
+
}
|
|
90
|
+
catch (e: HttpErrorResponse) {
|
|
91
|
+
this.errorService.sendError(e);
|
|
92
|
+
}
|
|
90
93
|
```
|
|
91
94
|
|
|
92
95
|
## Directives
|
|
@@ -99,13 +102,26 @@ Validates if two form fields match (useful for password confirmation).
|
|
|
99
102
|
```
|
|
100
103
|
|
|
101
104
|
### PasswordValidatorDirective
|
|
102
|
-
Ensures password meets
|
|
105
|
+
Ensures password meets the following requirments:
|
|
106
|
+
* At least one uppercase letter
|
|
107
|
+
* At least one lowercase letter
|
|
108
|
+
* At least one special character from the specified set
|
|
109
|
+
* At least one number
|
|
110
|
+
* Minimum length of 8 characters
|
|
111
|
+
|
|
103
112
|
```typescript
|
|
104
113
|
<input type="password" libPasswordValidator />
|
|
105
114
|
```
|
|
106
115
|
|
|
107
116
|
### PhoneValidationDirective
|
|
108
|
-
Formats and validates phone number input
|
|
117
|
+
Formats and validates phone number input as follows:
|
|
118
|
+
* Ensures the input always starts with a '+' symbol
|
|
119
|
+
If missing, automatically prepends it to the value
|
|
120
|
+
|
|
121
|
+
* Allows only numbers and the plus sign
|
|
122
|
+
|
|
123
|
+
* Prevents removing the initial '+' symbol:
|
|
124
|
+
|
|
109
125
|
```typescript
|
|
110
126
|
<input type="tel" libPhoneValidation />
|
|
111
127
|
```
|
|
@@ -129,18 +145,21 @@ Converts snake_case to Title Case text.
|
|
|
129
145
|
### LoaderService
|
|
130
146
|
Manages global loading state.
|
|
131
147
|
```typescript
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
148
|
+
private loaderService = inject(LoaderService);
|
|
149
|
+
this.loaderService.setLoading(true);
|
|
150
|
+
// do stuff
|
|
151
|
+
this.loaderService.setLoading(false);
|
|
137
152
|
```
|
|
138
153
|
|
|
139
154
|
### ErrorService
|
|
140
155
|
Handles global error display.
|
|
141
156
|
```typescript
|
|
142
|
-
|
|
143
|
-
|
|
157
|
+
private errorService = inject(ErrorService);
|
|
158
|
+
try {
|
|
159
|
+
this.apiCall();
|
|
160
|
+
}
|
|
161
|
+
catch (e: HttpErrorResponse) {
|
|
162
|
+
this.errorService.sendError(e);
|
|
144
163
|
}
|
|
145
164
|
```
|
|
146
165
|
|
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,44 +1,26 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ferhaps/easy-ui-lib",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "Angular UI components, directives and pipes library with Angular Material",
|
|
5
|
-
"keywords": [
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"@angular/cdk": "^19.0.5"
|
|
28
|
-
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"tslib": "^2.3.0"
|
|
31
|
-
},
|
|
32
|
-
"sideEffects": false,
|
|
33
|
-
"module": "fesm2022/ferhaps-easy-ui-lib.mjs",
|
|
34
|
-
"typings": "index.d.ts",
|
|
35
|
-
"exports": {
|
|
36
|
-
"./package.json": {
|
|
37
|
-
"default": "./package.json"
|
|
38
|
-
},
|
|
39
|
-
".": {
|
|
40
|
-
"types": "./index.d.ts",
|
|
41
|
-
"default": "./fesm2022/ferhaps-easy-ui-lib.mjs"
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@ferhaps/easy-ui-lib",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Angular UI components, directives and pipes library with Angular Material",
|
|
5
|
+
"keywords": ["angular", "ui", "components", "pipes", "directives", "library"],
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Ferhan Cherkez",
|
|
8
|
+
"email": "fericherkez14052002@gmail.com",
|
|
9
|
+
"url": "https://www.linkedin.com/in/ferhan-cherkez-6931a1190/"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/Ferhaps/angular-ui-library"
|
|
15
|
+
},
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@angular/common": "^19.0.6",
|
|
18
|
+
"@angular/core": "^19.0.6",
|
|
19
|
+
"@angular/material": "^19.0.5",
|
|
20
|
+
"@angular/cdk": "^19.0.5"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"tslib": "^2.3.0"
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<div class="modal" [style]="{'height': height()}">
|
|
2
|
+
<div class="dialog-title">
|
|
3
|
+
@if (withBack()) {
|
|
4
|
+
<div class="back-arrow" (click)="back.emit()">
|
|
5
|
+
<mat-icon>keyboard_arrow_left</mat-icon>
|
|
6
|
+
</div>
|
|
7
|
+
}
|
|
8
|
+
<h4 class="title">{{ dialogTitle() }}</h4>
|
|
9
|
+
<div class="closer" mat-dialog-close>
|
|
10
|
+
<mat-icon>close</mat-icon>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="dialog-content">
|
|
15
|
+
<ng-content select=".dialog-content"></ng-content>
|
|
16
|
+
<ng-template #tempBody></ng-template>
|
|
17
|
+
<ng-container *ngTemplateOutlet="temRef ? temRef: tempBody" />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.modal {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.dialog-title {
|
|
7
|
+
padding-inline: 2rem;
|
|
8
|
+
padding-top: 1rem;
|
|
9
|
+
margin-bottom: 1rem;
|
|
10
|
+
position: relative;
|
|
11
|
+
display: flex;
|
|
12
|
+
font-size: 20px;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
|
|
15
|
+
.back-arrow {
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
margin-right: 0.5rem;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.title {
|
|
21
|
+
font-weight: 700;
|
|
22
|
+
font-size: 24px;
|
|
23
|
+
margin: 0 !important;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.closer {
|
|
27
|
+
right: 3%;
|
|
28
|
+
position: absolute;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.dialog-content {
|
|
34
|
+
flex: 1;
|
|
35
|
+
min-height: 0;
|
|
36
|
+
overflow: hidden;
|
|
37
|
+
padding-bottom: 1rem;
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
align-items: center;
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Component, Input, input, output, TemplateRef } from '@angular/core';
|
|
2
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
3
|
+
import { MatDialogModule } from '@angular/material/dialog';
|
|
4
|
+
import { CommonModule } from '@angular/common';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'lib-default-dialog',
|
|
8
|
+
imports: [
|
|
9
|
+
CommonModule,
|
|
10
|
+
MatIconModule,
|
|
11
|
+
MatDialogModule
|
|
12
|
+
],
|
|
13
|
+
templateUrl: './default-dialog.component.html',
|
|
14
|
+
styleUrls: ['./default-dialog.component.scss']
|
|
15
|
+
})
|
|
16
|
+
export class DefaultDialogComponent {
|
|
17
|
+
@Input() temRef!: TemplateRef<any>;
|
|
18
|
+
public height = input<string>();
|
|
19
|
+
public dialogTitle = input<string>();
|
|
20
|
+
public withBack = input<boolean>();
|
|
21
|
+
|
|
22
|
+
protected back = output<void>();
|
|
23
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Component, input, OnInit } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
import { HttpErrorResponse } from '@angular/common/http';
|
|
4
|
+
import { SnakeCaseParserPipe } from '../pipes/snake-case-parser.pipe';
|
|
5
|
+
import { SystemError } from '../utils/types';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
selector: 'lib-error-dispaly',
|
|
9
|
+
imports: [
|
|
10
|
+
SnakeCaseParserPipe
|
|
11
|
+
],
|
|
12
|
+
template: `<strong class="err-container">{{ displayError | snakeCaseParser }}</strong>`,
|
|
13
|
+
styles: [`
|
|
14
|
+
.err-container {
|
|
15
|
+
display: block;
|
|
16
|
+
max-width: 300px;
|
|
17
|
+
font-size: 20px;
|
|
18
|
+
text-align: center;
|
|
19
|
+
border: 1px solid red;
|
|
20
|
+
border-radius: 5px;
|
|
21
|
+
padding: 0.5rem 1.5rem;
|
|
22
|
+
background-color: #ffe6e6;
|
|
23
|
+
color: #ff0000;
|
|
24
|
+
overflow-wrap: break-word;
|
|
25
|
+
}
|
|
26
|
+
`]
|
|
27
|
+
})
|
|
28
|
+
export class ErrorDispalyComponent implements OnInit {
|
|
29
|
+
public error = input.required<SystemError>();
|
|
30
|
+
|
|
31
|
+
protected displayError: string = '';
|
|
32
|
+
|
|
33
|
+
public ngOnInit(): void {
|
|
34
|
+
if (this.error() instanceof HttpErrorResponse) {
|
|
35
|
+
if (typeof (this.error() as HttpErrorResponse).error === 'string') {
|
|
36
|
+
this.displayError = (this.error() as HttpErrorResponse).error;
|
|
37
|
+
} else if (this.error && (this.error() as HttpErrorResponse)?.error?.message) {
|
|
38
|
+
this.displayError = (this.error() as HttpErrorResponse).error.message;
|
|
39
|
+
} else {
|
|
40
|
+
this.displayError = 'Unknown error';
|
|
41
|
+
}
|
|
42
|
+
} else if (typeof this.error() === 'string') {
|
|
43
|
+
this.displayError = (this.error() as string);
|
|
44
|
+
} else {
|
|
45
|
+
this.displayError = 'Unknown error';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { OnDestroy, inject } from '@angular/core';
|
|
2
|
+
import { Subscription } from 'rxjs';
|
|
3
|
+
import { Component } from '@angular/core';
|
|
4
|
+
import { HttpErrorResponse } from '@angular/common/http';
|
|
5
|
+
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
|
6
|
+
import { ErrorPopupComponent } from './error-popup/error-popup.component';
|
|
7
|
+
import { NoopScrollStrategy } from '@angular/cdk/overlay';
|
|
8
|
+
import { ErrorService } from '../../services/error.service';
|
|
9
|
+
|
|
10
|
+
@Component({
|
|
11
|
+
selector: 'lib-error-handler',
|
|
12
|
+
templateUrl: 'error-handler.component.html',
|
|
13
|
+
styleUrls: ['error-handler.component.scss'],
|
|
14
|
+
imports: [
|
|
15
|
+
MatDialogModule
|
|
16
|
+
]
|
|
17
|
+
})
|
|
18
|
+
export class ErrorHandlerComponent implements OnDestroy {
|
|
19
|
+
private errSubscriptions = new Subscription();
|
|
20
|
+
private errorService = inject(ErrorService);
|
|
21
|
+
private dialog = inject(MatDialog);
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
this.errSubscriptions.add(
|
|
25
|
+
this.errorService.error$.subscribe((err: HttpErrorResponse) => {
|
|
26
|
+
console.log(err);
|
|
27
|
+
this.showPopup(err);
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private showPopup(error: HttpErrorResponse): void {
|
|
33
|
+
this.dialog.closeAll();
|
|
34
|
+
this.dialog.open(ErrorPopupComponent, {
|
|
35
|
+
data: error,
|
|
36
|
+
width: '400px',
|
|
37
|
+
scrollStrategy: new NoopScrollStrategy(),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public ngOnDestroy(): void {
|
|
42
|
+
this.errSubscriptions.unsubscribe();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<lib-default-dialog [dialogTitle]="'Аn error has occurred'">
|
|
2
|
+
<div class="dialog-content">
|
|
3
|
+
<div class="status-info">
|
|
4
|
+
<div>{{ error.status }}</div>
|
|
5
|
+
@if (error.status) {
|
|
6
|
+
<div>{{ httpStatusCodes[error.status] }}</div>
|
|
7
|
+
}
|
|
8
|
+
</div>
|
|
9
|
+
<div class="error-info">
|
|
10
|
+
<lib-error-dispaly [error]="error" />
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</lib-default-dialog>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.dialog-content {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: center;
|
|
5
|
+
padding-inline: 1rem;
|
|
6
|
+
|
|
7
|
+
.status-info {
|
|
8
|
+
display: flex;
|
|
9
|
+
margin-bottom: 0.5rem;
|
|
10
|
+
font-weight: 600;
|
|
11
|
+
font-size: 18px;
|
|
12
|
+
gap: 0.5rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.error-info {
|
|
16
|
+
display: grid;
|
|
17
|
+
place-items: center;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Component, Inject } from '@angular/core';
|
|
2
|
+
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
|
3
|
+
import { HttpErrorResponse } from '@angular/common/http';
|
|
4
|
+
import { DefaultDialogComponent } from '../../default-dialog/default-dialog.component';
|
|
5
|
+
import { ErrorDispalyComponent } from '../../error-dispaly.component';
|
|
6
|
+
import { HTTP_STATUS_CODES } from '../../../utils/utils';
|
|
7
|
+
|
|
8
|
+
@Component({
|
|
9
|
+
selector: 'lib-error-popup',
|
|
10
|
+
imports: [
|
|
11
|
+
DefaultDialogComponent,
|
|
12
|
+
ErrorDispalyComponent
|
|
13
|
+
],
|
|
14
|
+
templateUrl: './error-popup.component.html',
|
|
15
|
+
styleUrls: ['./error-popup.component.scss']
|
|
16
|
+
})
|
|
17
|
+
export class ErrorPopupComponent {
|
|
18
|
+
protected httpStatusCodes = HTTP_STATUS_CODES;
|
|
19
|
+
|
|
20
|
+
constructor(@Inject(MAT_DIALOG_DATA) public error: HttpErrorResponse) { }
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Component, inject } from '@angular/core';
|
|
2
|
+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
3
|
+
import { LoaderService } from '../../services/loader.service';
|
|
4
|
+
import { AsyncPipe } from '@angular/common';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'lib-global-loader',
|
|
8
|
+
imports: [
|
|
9
|
+
AsyncPipe,
|
|
10
|
+
MatProgressSpinnerModule
|
|
11
|
+
],
|
|
12
|
+
templateUrl: './global-loader.component.html',
|
|
13
|
+
styleUrl: './global-loader.component.scss'
|
|
14
|
+
})
|
|
15
|
+
export class GlobalLoaderComponent {
|
|
16
|
+
protected loaderService = inject(LoaderService);
|
|
17
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Component, output, input } from '@angular/core';
|
|
2
|
+
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
|
3
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
4
|
+
import { debounceTime, distinctUntilChanged } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'lib-search-bar',
|
|
8
|
+
template: `
|
|
9
|
+
<form class="search-bar" [formGroup]="searchForm">
|
|
10
|
+
<mat-icon>search</mat-icon>
|
|
11
|
+
<input class="search-input" type="search" name="field"
|
|
12
|
+
[placeholder]="'Search ' + for()" autocomplete="off" formControlName="search" />
|
|
13
|
+
</form>
|
|
14
|
+
`,
|
|
15
|
+
styles: [`
|
|
16
|
+
.search-bar {
|
|
17
|
+
width: 270px;
|
|
18
|
+
border: 1px solid #A4A4A4;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.search-input {
|
|
24
|
+
border: none;
|
|
25
|
+
padding: 7px 11px;
|
|
26
|
+
height: 100%;
|
|
27
|
+
width: 100%;
|
|
28
|
+
background-color: transparent;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
mat-icon {
|
|
32
|
+
margin-inline: 8px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.search-input:focus {
|
|
36
|
+
border: none;
|
|
37
|
+
outline: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@media (max-width: 1086px) {
|
|
41
|
+
.search-bar {
|
|
42
|
+
width: 170px;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
`],
|
|
46
|
+
imports: [
|
|
47
|
+
MatIconModule,
|
|
48
|
+
ReactiveFormsModule
|
|
49
|
+
]
|
|
50
|
+
})
|
|
51
|
+
export class SearchBarComponent {
|
|
52
|
+
public for = input.required<string>();
|
|
53
|
+
protected search = output<string>();
|
|
54
|
+
|
|
55
|
+
protected searchForm: FormGroup = new FormGroup({
|
|
56
|
+
search: new FormControl('')
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
constructor() {
|
|
60
|
+
this.searchForm.get('search')?.valueChanges.
|
|
61
|
+
pipe(
|
|
62
|
+
debounceTime(1000),
|
|
63
|
+
distinctUntilChanged(),
|
|
64
|
+
).subscribe((searchTerm: string) => {
|
|
65
|
+
if (searchTerm.trim() !== '') {
|
|
66
|
+
this.search.emit(searchTerm);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<div class="flexer">
|
|
2
|
+
@if (config().title) {
|
|
3
|
+
<div class="row-heading-labels mb05">{{config().title}}</div>
|
|
4
|
+
}
|
|
5
|
+
<ng-content select=".upper-part"></ng-content>
|
|
6
|
+
@if (config().withAdd) {
|
|
7
|
+
<div class="flexer action pointer gap05 mb05" (click)="action.emit({action: 'add'})">
|
|
8
|
+
<mat-icon>add_circle_outline</mat-icon>
|
|
9
|
+
<div>add</div>
|
|
10
|
+
</div>
|
|
11
|
+
}
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="scroll" #scrollContainer (scroll)="onScroll()">
|
|
15
|
+
<table [class.with-options]="config().options?.length">
|
|
16
|
+
<thead>
|
|
17
|
+
<tr>
|
|
18
|
+
@for (heading of config().tableHeadings; track heading; let i = $index) {
|
|
19
|
+
<th>
|
|
20
|
+
<div class="flexer gap05">
|
|
21
|
+
{{heading}}
|
|
22
|
+
@if (config().sortable && !config().draggable) {
|
|
23
|
+
<lib-table-sort-header
|
|
24
|
+
[selected]="currentSortColumn === i"
|
|
25
|
+
(click)="currentSortColumn = i"
|
|
26
|
+
(sort)="sortByProp(config().dataProps[i], $event)"
|
|
27
|
+
/>
|
|
28
|
+
}
|
|
29
|
+
</div>
|
|
30
|
+
</th>
|
|
31
|
+
}
|
|
32
|
+
@if (config().options?.length) {
|
|
33
|
+
<th></th>
|
|
34
|
+
}
|
|
35
|
+
</tr>
|
|
36
|
+
</thead>
|
|
37
|
+
@if (config().draggable) {
|
|
38
|
+
<tbody cdkDropList (cdkDropListDropped)="drop($event)">
|
|
39
|
+
@for (obj of config().data; track trackById(i, obj); let i = $index) {
|
|
40
|
+
<tr
|
|
41
|
+
(mouseover)="hoverRowIndex = i"
|
|
42
|
+
(mouseleave)="hoverRowIndex = -1"
|
|
43
|
+
cdkDrag cdkDragLockAxis="y"
|
|
44
|
+
[class.pointer]="config().selectableRows"
|
|
45
|
+
[class.hover-row]="hoverRowIndex == i"
|
|
46
|
+
[class.selected-row]="config().selectableRows && selectedRowIndex == i"
|
|
47
|
+
(click)="onRowClick($event, obj, i)"
|
|
48
|
+
@fadeInOut>
|
|
49
|
+
@for (prop of config().dataProps; track prop; let cellIndex = $index) {
|
|
50
|
+
<td
|
|
51
|
+
[class.dragCol]="cellIndex === 0">
|
|
52
|
+
<div class="data" [classList]="getClass(obj, prop)"
|
|
53
|
+
[class.flexer]="cellIndex === 0">
|
|
54
|
+
@if (cellIndex === 0) {
|
|
55
|
+
<mat-icon class="draggable" cdkDragHandle>
|
|
56
|
+
drag_indicator
|
|
57
|
+
</mat-icon>
|
|
58
|
+
}
|
|
59
|
+
{{obj[prop] | blankFiller}}
|
|
60
|
+
</div>
|
|
61
|
+
</td>
|
|
62
|
+
}
|
|
63
|
+
@if (config().options?.length) {
|
|
64
|
+
<td class="right-align" (click)="$event.stopPropagation()">
|
|
65
|
+
<button mat-icon-button class="pointer dots right" [matMenuTriggerFor]="optionsMenu">
|
|
66
|
+
<mat-icon>more_vert</mat-icon>
|
|
67
|
+
</button>
|
|
68
|
+
<mat-menu #optionsMenu="matMenu">
|
|
69
|
+
@for (option of config().options; track option) {
|
|
70
|
+
<div
|
|
71
|
+
mat-menu-item (click)="selectOption(option, obj, i)"
|
|
72
|
+
[class.red]="option == 'Remove' || option == 'Delete'">
|
|
73
|
+
{{option}}
|
|
74
|
+
</div>
|
|
75
|
+
}
|
|
76
|
+
</mat-menu>
|
|
77
|
+
</td>
|
|
78
|
+
}
|
|
79
|
+
</tr>
|
|
80
|
+
}
|
|
81
|
+
</tbody>
|
|
82
|
+
} @else {
|
|
83
|
+
<tbody>
|
|
84
|
+
@for (obj of config().data; track trackById(i, obj); let i = $index) {
|
|
85
|
+
<tr
|
|
86
|
+
(mouseover)="hoverRowIndex = i"
|
|
87
|
+
(mouseleave)="hoverRowIndex = -1"
|
|
88
|
+
[class.pointer]="config().selectableRows"
|
|
89
|
+
[class.hover-row]="hoverRowIndex == i"
|
|
90
|
+
[class.selected-row]="config().selectableRows && selectedRowIndex == i"
|
|
91
|
+
(click)="onRowClick($event, obj, i)"
|
|
92
|
+
@fadeInOut>
|
|
93
|
+
@for (prop of config().dataProps; track prop; let cellIndex = $index) {
|
|
94
|
+
<td>
|
|
95
|
+
<div class="data" [classList]="getClass(obj, prop)">
|
|
96
|
+
{{obj[prop] | blankFiller}}
|
|
97
|
+
</div>
|
|
98
|
+
</td>
|
|
99
|
+
}
|
|
100
|
+
@if (config().options?.length) {
|
|
101
|
+
<td class="right-align" (click)="$event.stopPropagation()">
|
|
102
|
+
<button mat-icon-button class="pointer dots right" [matMenuTriggerFor]="optionsMenu">
|
|
103
|
+
<mat-icon>more_vert</mat-icon>
|
|
104
|
+
</button>
|
|
105
|
+
<mat-menu #optionsMenu="matMenu">
|
|
106
|
+
@for (option of config().options; track option) {
|
|
107
|
+
<div
|
|
108
|
+
mat-menu-item (click)="selectOption(option, obj, i)"
|
|
109
|
+
[class.red]="option == 'Remove' || option == 'Delete'">
|
|
110
|
+
{{option}}
|
|
111
|
+
</div>
|
|
112
|
+
}
|
|
113
|
+
</mat-menu>
|
|
114
|
+
</td>
|
|
115
|
+
}
|
|
116
|
+
</tr>
|
|
117
|
+
}
|
|
118
|
+
</tbody>
|
|
119
|
+
}
|
|
120
|
+
</table>
|
|
121
|
+
</div>
|