@verisoft/store 0.0.1
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/.eslintrc.json +43 -0
- package/README.md +7 -0
- package/jest.config.ts +22 -0
- package/ng-package.json +7 -0
- package/package.json +12 -0
- package/project.json +36 -0
- package/src/index.ts +9 -0
- package/src/lib/detail-state/detail.actions.ts +63 -0
- package/src/lib/detail-state/detail.effects.ts +131 -0
- package/src/lib/detail-state/detail.models.ts +32 -0
- package/src/lib/detail-state/detail.reducer.ts +122 -0
- package/src/lib/table-state/actions.ts +93 -0
- package/src/lib/table-state/effects.ts +100 -0
- package/src/lib/table-state/models.ts +20 -0
- package/src/lib/table-state/reducers.ts +126 -0
- package/src/test-setup.ts +8 -0
- package/tsconfig.json +29 -0
- package/tsconfig.lib.json +17 -0
- package/tsconfig.lib.prod.json +9 -0
- package/tsconfig.spec.json +16 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["../../../.eslintrc.base.json"],
|
|
3
|
+
"ignorePatterns": ["!**/*"],
|
|
4
|
+
"overrides": [
|
|
5
|
+
{
|
|
6
|
+
"files": ["*.ts"],
|
|
7
|
+
"extends": [
|
|
8
|
+
"plugin:@nx/angular",
|
|
9
|
+
"plugin:@angular-eslint/template/process-inline-templates"
|
|
10
|
+
],
|
|
11
|
+
"rules": {
|
|
12
|
+
"@angular-eslint/directive-selector": [
|
|
13
|
+
"error",
|
|
14
|
+
{
|
|
15
|
+
"type": "attribute",
|
|
16
|
+
"prefix": "lib",
|
|
17
|
+
"style": "camelCase"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"@angular-eslint/component-selector": [
|
|
21
|
+
"error",
|
|
22
|
+
{
|
|
23
|
+
"type": "element",
|
|
24
|
+
"prefix": "lib",
|
|
25
|
+
"style": "kebab-case"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"files": ["*.html"],
|
|
32
|
+
"extends": ["plugin:@nx/angular-template"],
|
|
33
|
+
"rules": {}
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"files": ["*.json"],
|
|
37
|
+
"parser": "jsonc-eslint-parser",
|
|
38
|
+
"rules": {
|
|
39
|
+
"@nx/dependency-checks": "error"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
package/README.md
ADDED
package/jest.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
export default {
|
|
3
|
+
displayName: 'store',
|
|
4
|
+
preset: '../../../jest.preset.js',
|
|
5
|
+
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
|
6
|
+
coverageDirectory: '../../../coverage/src/libs/store',
|
|
7
|
+
transform: {
|
|
8
|
+
'^.+\\.(ts|mjs|js|html)$': [
|
|
9
|
+
'jest-preset-angular',
|
|
10
|
+
{
|
|
11
|
+
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
12
|
+
stringifyContentPathRegex: '\\.(html|svg)$',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
|
17
|
+
snapshotSerializers: [
|
|
18
|
+
'jest-preset-angular/build/serializers/no-ng-attributes',
|
|
19
|
+
'jest-preset-angular/build/serializers/ng-snapshot',
|
|
20
|
+
'jest-preset-angular/build/serializers/html-comment',
|
|
21
|
+
],
|
|
22
|
+
};
|
package/ng-package.json
ADDED
package/package.json
ADDED
package/project.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "store",
|
|
3
|
+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "src/libs/store/src",
|
|
5
|
+
"prefix": "lib",
|
|
6
|
+
"projectType": "library",
|
|
7
|
+
"tags": [],
|
|
8
|
+
"targets": {
|
|
9
|
+
"build": {
|
|
10
|
+
"executor": "@nx/angular:package",
|
|
11
|
+
"outputs": ["{workspaceRoot}/dist/{projectRoot}"],
|
|
12
|
+
"options": {
|
|
13
|
+
"project": "src/libs/store/ng-package.json"
|
|
14
|
+
},
|
|
15
|
+
"configurations": {
|
|
16
|
+
"production": {
|
|
17
|
+
"tsConfig": "src/libs/store/tsconfig.lib.prod.json"
|
|
18
|
+
},
|
|
19
|
+
"development": {
|
|
20
|
+
"tsConfig": "src/libs/store/tsconfig.lib.json"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"defaultConfiguration": "production"
|
|
24
|
+
},
|
|
25
|
+
"test": {
|
|
26
|
+
"executor": "@nx/jest:jest",
|
|
27
|
+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
28
|
+
"options": {
|
|
29
|
+
"jestConfig": "src/libs/store/jest.config.ts"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"lint": {
|
|
33
|
+
"executor": "@nx/eslint:lint"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './lib/detail-state/detail.actions';
|
|
2
|
+
export * from './lib/detail-state/detail.reducer';
|
|
3
|
+
export * from './lib/detail-state/detail.effects';
|
|
4
|
+
export * from './lib/detail-state/detail.models';
|
|
5
|
+
|
|
6
|
+
export * from './lib/table-state/actions';
|
|
7
|
+
export * from './lib/table-state/reducers';
|
|
8
|
+
export * from './lib/table-state/effects';
|
|
9
|
+
export * from './lib/table-state/models';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { createAction, props } from '@ngrx/store';
|
|
2
|
+
import { FormState } from './detail.models';
|
|
3
|
+
|
|
4
|
+
export function createInitDetailAction(detailsRepository: string) {
|
|
5
|
+
return createAction(
|
|
6
|
+
`[${detailsRepository} Page] Init`,
|
|
7
|
+
props<{ obj: string | null | number | undefined | any }>()
|
|
8
|
+
);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createLoadDetailSuccessAction<T>(detailsRepository: string) {
|
|
12
|
+
return createAction(
|
|
13
|
+
`[${detailsRepository}/API] Load ${detailsRepository} Success`,
|
|
14
|
+
props<{ item: T }>()
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createLoadDetailFailureAction(detailsRepository: string) {
|
|
19
|
+
return createAction(
|
|
20
|
+
`[${detailsRepository}/API] Load ${detailsRepository} Failure`,
|
|
21
|
+
props<{ error: any }>()
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createUpdateDetailAction<T>(detailsRepository: string) {
|
|
26
|
+
return createAction(
|
|
27
|
+
`[${detailsRepository}/API] Update ${detailsRepository}`,
|
|
28
|
+
props<{ item: T }>()
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createUpdateFormStateAction(detailsRepository: string) {
|
|
33
|
+
return createAction(
|
|
34
|
+
`[${detailsRepository}/API] Update Form State`,
|
|
35
|
+
props<{ formState: FormState }>()
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createSaveDetailAction(detailsRepository: string) {
|
|
40
|
+
return createAction(`[${detailsRepository}/API] Save Detail `);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createSaveDetailSuccessAction<T>(detailsRepository: string) {
|
|
44
|
+
return createAction(
|
|
45
|
+
`[${detailsRepository}/API] Save Detail Success`,
|
|
46
|
+
props<{ item: T }>()
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createSaveDetailFailureAction(detailsRepository: string) {
|
|
51
|
+
return createAction(
|
|
52
|
+
`[${detailsRepository}/API] Save Detail Failure`,
|
|
53
|
+
props<{ error: any }>()
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createUpdateSaveDetailAction(detailsRepository: string) {
|
|
58
|
+
return createAction(`[${detailsRepository}/API] Update Detail `);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createResetStateAction(detailsRepository: string) {
|
|
62
|
+
return createAction(`[${detailsRepository}/API] Reset State`);
|
|
63
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
|
2
|
+
import { BaseHttpService } from '@verisoft/core';
|
|
3
|
+
import {
|
|
4
|
+
catchError,
|
|
5
|
+
EMPTY,
|
|
6
|
+
map,
|
|
7
|
+
Observable,
|
|
8
|
+
of,
|
|
9
|
+
switchMap,
|
|
10
|
+
tap,
|
|
11
|
+
withLatestFrom,
|
|
12
|
+
} from 'rxjs';
|
|
13
|
+
import {
|
|
14
|
+
createInitDetailAction,
|
|
15
|
+
createLoadDetailFailureAction,
|
|
16
|
+
createLoadDetailSuccessAction,
|
|
17
|
+
createSaveDetailAction,
|
|
18
|
+
createSaveDetailFailureAction,
|
|
19
|
+
createSaveDetailSuccessAction,
|
|
20
|
+
} from './detail.actions';
|
|
21
|
+
|
|
22
|
+
export interface CreateInitFormConfig<T> {
|
|
23
|
+
service?: BaseHttpService<T>;
|
|
24
|
+
get?: (obj: any) => Observable<T>;
|
|
25
|
+
snackbar?: any;
|
|
26
|
+
errorMessage?: string;
|
|
27
|
+
successMessge?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createInitDetailEffect<T>(
|
|
31
|
+
detailsRepository: string,
|
|
32
|
+
actions$: Actions,
|
|
33
|
+
config: CreateInitFormConfig<T>
|
|
34
|
+
) {
|
|
35
|
+
return createEffect(() => {
|
|
36
|
+
return actions$.pipe(
|
|
37
|
+
ofType(createInitDetailAction(detailsRepository)),
|
|
38
|
+
switchMap(({ obj }) => {
|
|
39
|
+
const get$: Observable<any> = (config?.service?.get(obj as any) ??
|
|
40
|
+
config?.get?.(obj)) as Observable<any>;
|
|
41
|
+
if(!get$) return EMPTY;
|
|
42
|
+
return get$?.pipe(
|
|
43
|
+
map((item) => {
|
|
44
|
+
return createLoadDetailSuccessAction(detailsRepository)({ item });
|
|
45
|
+
}),
|
|
46
|
+
catchError((error) => {
|
|
47
|
+
console.error('Error', error);
|
|
48
|
+
config?.snackbar?.showError(config?.errorMessage ?? error);
|
|
49
|
+
return of(
|
|
50
|
+
createLoadDetailFailureAction(detailsRepository)({ error })
|
|
51
|
+
);
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createSaveDetailEffect<
|
|
60
|
+
T extends { id: number },
|
|
61
|
+
SERVICE extends BaseHttpService<T>
|
|
62
|
+
>(
|
|
63
|
+
detailsRepository: string,
|
|
64
|
+
actions$: Actions,
|
|
65
|
+
service: SERVICE,
|
|
66
|
+
detail$: Observable<T | undefined>,
|
|
67
|
+
) {
|
|
68
|
+
return createEffect(() =>
|
|
69
|
+
actions$.pipe(
|
|
70
|
+
ofType(createSaveDetailAction(detailsRepository)),
|
|
71
|
+
withLatestFrom(detail$),
|
|
72
|
+
switchMap(([, savedItem]) => {
|
|
73
|
+
const isNew = !savedItem?.id;
|
|
74
|
+
const saveAction = isNew
|
|
75
|
+
? service.post(savedItem as T)
|
|
76
|
+
: service.put(savedItem.id, savedItem);
|
|
77
|
+
return saveAction.pipe(
|
|
78
|
+
map((item) => {
|
|
79
|
+
return createSaveDetailSuccessAction(detailsRepository)({
|
|
80
|
+
item: item,
|
|
81
|
+
});
|
|
82
|
+
}),
|
|
83
|
+
catchError((error) => {
|
|
84
|
+
console.error('Error', error);
|
|
85
|
+
return of(
|
|
86
|
+
createSaveDetailFailureAction(detailsRepository)({ error })
|
|
87
|
+
);
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
})
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function createSaveDetailSuccessEffect(
|
|
96
|
+
detailsRepository: string,
|
|
97
|
+
actions$: Actions,
|
|
98
|
+
config?: CreateInitFormConfig<unknown>,
|
|
99
|
+
) {
|
|
100
|
+
return createEffect(
|
|
101
|
+
() =>
|
|
102
|
+
actions$.pipe(
|
|
103
|
+
ofType(createSaveDetailSuccessAction(detailsRepository)),
|
|
104
|
+
tap(() => {
|
|
105
|
+
config?.snackbar?.showSuccess(config?.successMessge ?? `Item successfully saved.`);
|
|
106
|
+
})
|
|
107
|
+
),
|
|
108
|
+
{
|
|
109
|
+
dispatch: false,
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function createSaveDetailFailureEffect(
|
|
115
|
+
detailsRepository: string,
|
|
116
|
+
actions$: Actions,
|
|
117
|
+
config?: CreateInitFormConfig<unknown>,
|
|
118
|
+
) {
|
|
119
|
+
return createEffect(
|
|
120
|
+
() =>
|
|
121
|
+
actions$.pipe(
|
|
122
|
+
ofType(createSaveDetailFailureAction(detailsRepository)),
|
|
123
|
+
tap(({error}) => {
|
|
124
|
+
config?.snackbar?.showError(config.errorMessage ?? `Error saving item. ${error.message}`);
|
|
125
|
+
})
|
|
126
|
+
),
|
|
127
|
+
{
|
|
128
|
+
dispatch: false,
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface DetailState<T> {
|
|
2
|
+
loaded: boolean;
|
|
3
|
+
item?: T;
|
|
4
|
+
formState?: FormState;
|
|
5
|
+
error?: string | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ChosenRowsState<T> {
|
|
9
|
+
rows: Array<T>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const INITIAL_CHOSEN_ROW_STATE: ChosenRowsState<any> = {
|
|
13
|
+
rows: []
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const INITIAL_DETAIL_STATE: DetailState<any> = {
|
|
17
|
+
loaded: false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface SaveItemState {
|
|
21
|
+
saveInProgress: boolean;
|
|
22
|
+
error?: string | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const INITIAL_SAVE_ITEM_STATE: SaveItemState = {
|
|
26
|
+
saveInProgress: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export interface FormState {
|
|
30
|
+
dirty: boolean;
|
|
31
|
+
valid: boolean;
|
|
32
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ActionCreator, createReducer, on, ReducerTypes } from '@ngrx/store';
|
|
2
|
+
import {
|
|
3
|
+
createInitDetailAction,
|
|
4
|
+
createLoadDetailFailureAction,
|
|
5
|
+
createLoadDetailSuccessAction,
|
|
6
|
+
createResetStateAction,
|
|
7
|
+
createSaveDetailAction,
|
|
8
|
+
createSaveDetailFailureAction,
|
|
9
|
+
createSaveDetailSuccessAction,
|
|
10
|
+
createUpdateDetailAction,
|
|
11
|
+
createUpdateFormStateAction,
|
|
12
|
+
} from './detail.actions';
|
|
13
|
+
import {
|
|
14
|
+
DetailState,
|
|
15
|
+
INITIAL_DETAIL_STATE,
|
|
16
|
+
INITIAL_SAVE_ITEM_STATE,
|
|
17
|
+
} from './detail.models';
|
|
18
|
+
|
|
19
|
+
export function createDetailReducers<
|
|
20
|
+
T = any,
|
|
21
|
+
TState extends DetailState<T> = DetailState<T>
|
|
22
|
+
>(
|
|
23
|
+
detailRepository: string,
|
|
24
|
+
initialState: TState = INITIAL_DETAIL_STATE as unknown as TState,
|
|
25
|
+
...ons: ReducerTypes<TState, readonly ActionCreator[]>[]
|
|
26
|
+
) {
|
|
27
|
+
return createReducer(
|
|
28
|
+
initialState,
|
|
29
|
+
on(createInitDetailAction(detailRepository), (state) => {
|
|
30
|
+
return {
|
|
31
|
+
...state,
|
|
32
|
+
loaded: false,
|
|
33
|
+
error: null,
|
|
34
|
+
};
|
|
35
|
+
}),
|
|
36
|
+
|
|
37
|
+
on(createLoadDetailSuccessAction(detailRepository), (state, { item }) => {
|
|
38
|
+
return {
|
|
39
|
+
...state,
|
|
40
|
+
loaded: true,
|
|
41
|
+
item,
|
|
42
|
+
};
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
on(createLoadDetailFailureAction(detailRepository), (state, { error }) => {
|
|
46
|
+
return {
|
|
47
|
+
...state,
|
|
48
|
+
loaded: true,
|
|
49
|
+
error,
|
|
50
|
+
};
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
on(createUpdateDetailAction(detailRepository), (state, { item }) => {
|
|
54
|
+
return {
|
|
55
|
+
...state,
|
|
56
|
+
item,
|
|
57
|
+
formState: {
|
|
58
|
+
dirty: true,
|
|
59
|
+
valid: state.formState?.valid ?? true,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}),
|
|
63
|
+
|
|
64
|
+
on(
|
|
65
|
+
createUpdateFormStateAction(detailRepository),
|
|
66
|
+
(state, { formState }) => {
|
|
67
|
+
return {
|
|
68
|
+
...state,
|
|
69
|
+
formState,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
),
|
|
73
|
+
|
|
74
|
+
on(createSaveDetailSuccessAction(detailRepository), (state) => {
|
|
75
|
+
return {
|
|
76
|
+
...state,
|
|
77
|
+
formState: {
|
|
78
|
+
dirty: false,
|
|
79
|
+
valid: state.formState?.valid ?? true,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}),
|
|
83
|
+
on(createResetStateAction(detailRepository), () => {
|
|
84
|
+
return {
|
|
85
|
+
...(initialState as any),
|
|
86
|
+
};
|
|
87
|
+
}),
|
|
88
|
+
...ons
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createSaveDetailReducers(
|
|
93
|
+
detailRepository: string,
|
|
94
|
+
initialState = INITIAL_SAVE_ITEM_STATE
|
|
95
|
+
) {
|
|
96
|
+
return createReducer(
|
|
97
|
+
initialState,
|
|
98
|
+
|
|
99
|
+
on(createSaveDetailAction(detailRepository), (state) => {
|
|
100
|
+
return {
|
|
101
|
+
...state,
|
|
102
|
+
saveInProgress: true,
|
|
103
|
+
};
|
|
104
|
+
}),
|
|
105
|
+
|
|
106
|
+
on(createSaveDetailSuccessAction(detailRepository), (state, { item }) => {
|
|
107
|
+
return {
|
|
108
|
+
...state,
|
|
109
|
+
item,
|
|
110
|
+
saveInProgress: false,
|
|
111
|
+
};
|
|
112
|
+
}),
|
|
113
|
+
|
|
114
|
+
on(createSaveDetailFailureAction(detailRepository), (state, { error }) => {
|
|
115
|
+
return {
|
|
116
|
+
...state,
|
|
117
|
+
error,
|
|
118
|
+
saveInProgress: false,
|
|
119
|
+
};
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createAction, props } from '@ngrx/store';
|
|
2
|
+
import { Page, Sort } from '@verisoft/core';
|
|
3
|
+
|
|
4
|
+
enum TablePageAction {
|
|
5
|
+
GET_PAGE = 'Get page',
|
|
6
|
+
CREATE_STATIC_FILTER = 'Create static filter',
|
|
7
|
+
DATA_LOAD_SUCCESS = 'Data load success',
|
|
8
|
+
DATA_LOAD_ERROR = 'Data load error',
|
|
9
|
+
REFRESH_PAGE = 'Refresh page',
|
|
10
|
+
FILTER_PAGE = 'Filter page',
|
|
11
|
+
CHANGE_PAGE_SIZE = 'Change page size',
|
|
12
|
+
SORT_PAGE = 'Sort page',
|
|
13
|
+
DESTROY = 'Destroy',
|
|
14
|
+
SELECT_ITEMS = 'Select items',
|
|
15
|
+
REMOVE_RANGE = 'Remove range',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createGetPageTableAction<T = any>(tableRepository: string) {
|
|
19
|
+
return createAction(
|
|
20
|
+
`${tableRepository} ${TablePageAction.GET_PAGE}`,
|
|
21
|
+
props<{
|
|
22
|
+
page: number;
|
|
23
|
+
size: number;
|
|
24
|
+
id?: string | null;
|
|
25
|
+
filter?: Partial<T>;
|
|
26
|
+
sort?: Sort<T>[];
|
|
27
|
+
}>()
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createStaticFilterTableAction<T = any>(
|
|
32
|
+
tableRepository: string
|
|
33
|
+
) {
|
|
34
|
+
return createAction(
|
|
35
|
+
`${tableRepository} ${TablePageAction.CREATE_STATIC_FILTER}`,
|
|
36
|
+
props<{
|
|
37
|
+
filter?: Partial<T>;
|
|
38
|
+
}>()
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createDataLoadSuccessTableAction<T>(tableRepository: string) {
|
|
43
|
+
return createAction(
|
|
44
|
+
`${tableRepository} ${TablePageAction.DATA_LOAD_SUCCESS}`,
|
|
45
|
+
props<{ gPage: Page<T> }>()
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createDataLoadErrorTableAction(tableRepository: string) {
|
|
50
|
+
return createAction(
|
|
51
|
+
`${tableRepository} ${TablePageAction.DATA_LOAD_ERROR}`,
|
|
52
|
+
props<{ error: any }>()
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createChangePageSizeTableAction(tableRepository: string) {
|
|
57
|
+
return createAction(
|
|
58
|
+
`${tableRepository} ${TablePageAction.CHANGE_PAGE_SIZE}`,
|
|
59
|
+
props<{ size: number }>()
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// TODO: use action in delete item effect
|
|
64
|
+
export function createRefreshPageTableAction(tableRepository: string) {
|
|
65
|
+
return createAction(`${tableRepository} ${TablePageAction.REFRESH_PAGE}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createFilterPageTableAction<T>(tableRepository: string) {
|
|
69
|
+
return createAction(
|
|
70
|
+
`${tableRepository} ${TablePageAction.REFRESH_PAGE}`,
|
|
71
|
+
props<{ filter: Partial<T> }>()
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createResetTableFilterAction(tableRepository: string) {
|
|
76
|
+
return createAction(`${tableRepository} ${TablePageAction.REFRESH_PAGE}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createDestroyTableAction(tableRepository: string) {
|
|
80
|
+
return createAction(`${tableRepository} ${TablePageAction.DESTROY}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function createSelectItemsTableAction<T>(tableRepository: string) {
|
|
84
|
+
return createAction(
|
|
85
|
+
`${tableRepository} ${TablePageAction.SELECT_ITEMS}`,
|
|
86
|
+
props<{ selectedItems: T[] }>()
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createRemoveRangeTableAction(tableRepository: string) {
|
|
91
|
+
return createAction(`${tableRepository} ${TablePageAction.REMOVE_RANGE}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Actions, createEffect, ofType } from '@ngrx/effects';
|
|
2
|
+
import { createFeatureSelector, createSelector, Store } from '@ngrx/store';
|
|
3
|
+
import { BaseHttpService, DEFAULT_SEARCH_LIMIT, Page, RequestParams } from '@verisoft/core';
|
|
4
|
+
import { catchError, map, Observable, of, switchMap, withLatestFrom } from 'rxjs';
|
|
5
|
+
import {
|
|
6
|
+
createGetPageTableAction,
|
|
7
|
+
createDataLoadSuccessTableAction,
|
|
8
|
+
createDataLoadErrorTableAction,
|
|
9
|
+
createRemoveRangeTableAction,
|
|
10
|
+
} from './actions';
|
|
11
|
+
import { TableState } from './models';
|
|
12
|
+
|
|
13
|
+
export interface CreateGetPageActionConfig<T> {
|
|
14
|
+
service?: BaseHttpService<T>;
|
|
15
|
+
fetchList?: (requestParams: RequestParams<any>) => Observable<Page<T>>;
|
|
16
|
+
snackbar?: any;
|
|
17
|
+
ngrxFeatureKey?: string;
|
|
18
|
+
requireFilters?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createGetPageTableEffect<T>(
|
|
22
|
+
tableRepository: string,
|
|
23
|
+
actions$: Actions,
|
|
24
|
+
config: CreateGetPageActionConfig<T>
|
|
25
|
+
) {
|
|
26
|
+
return createEffect(() => {
|
|
27
|
+
return actions$.pipe(
|
|
28
|
+
ofType(createGetPageTableAction(tableRepository)),
|
|
29
|
+
switchMap(({ page, filter, sort, size }) => {
|
|
30
|
+
const requestParams: RequestParams<any> = {
|
|
31
|
+
page,
|
|
32
|
+
sort,
|
|
33
|
+
filter,
|
|
34
|
+
size,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const params = config.service?.createParams(requestParams) ?? requestParams;
|
|
38
|
+
|
|
39
|
+
const fetchList$: Observable<Page<T>> = (config.service?.fetchList(
|
|
40
|
+
params
|
|
41
|
+
) ?? config.fetchList?.(params)) as Observable<Page<T>>;
|
|
42
|
+
if (!fetchList$) {
|
|
43
|
+
throw new Error('Service or fetchList$ must by defined.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return fetchList$.pipe(
|
|
47
|
+
map((gPage) => {
|
|
48
|
+
const p = { ...gPage, number: page };
|
|
49
|
+
return createDataLoadSuccessTableAction(tableRepository)({
|
|
50
|
+
gPage: p,
|
|
51
|
+
});
|
|
52
|
+
}),
|
|
53
|
+
catchError((error) => {
|
|
54
|
+
config.snackbar?.showError(error.message);
|
|
55
|
+
return of(
|
|
56
|
+
createDataLoadErrorTableAction(tableRepository)({ error })
|
|
57
|
+
);
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createRemoveRangeTableEffect<T>(
|
|
66
|
+
tableRepository: string,
|
|
67
|
+
ngrxFeatureKey: string,
|
|
68
|
+
actions$: Actions,
|
|
69
|
+
store$: Store<any>,
|
|
70
|
+
config: CreateGetPageActionConfig<T>
|
|
71
|
+
) {
|
|
72
|
+
return createEffect(() => {
|
|
73
|
+
if(!config?.service) {
|
|
74
|
+
throw new Error('Service must be defined!');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const selectedItems = createSelector(
|
|
78
|
+
createFeatureSelector<any>(ngrxFeatureKey),
|
|
79
|
+
(state: any) => {
|
|
80
|
+
return (state?.[tableRepository] as TableState<any>)?.selectedItems as any;
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return actions$.pipe(
|
|
85
|
+
ofType(createRemoveRangeTableAction(tableRepository)),
|
|
86
|
+
withLatestFrom(selectedItems),
|
|
87
|
+
switchMap(([, {selectedItems}]) => {
|
|
88
|
+
return config.service!.removeRange(selectedItems).pipe(
|
|
89
|
+
map(() => {
|
|
90
|
+
return createGetPageTableAction(tableRepository)
|
|
91
|
+
({ page: 0, size: DEFAULT_SEARCH_LIMIT })
|
|
92
|
+
}),
|
|
93
|
+
catchError(error => {
|
|
94
|
+
return of(createDataLoadErrorTableAction(tableRepository)({ error }))
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
})
|
|
98
|
+
);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DEFAULT_SEARCH_LIMIT, DEFAULT_SEARCH_PARAMS, Page, RequestParams } from "@verisoft/core";
|
|
2
|
+
|
|
3
|
+
export interface TableState<T> {
|
|
4
|
+
dataLoading: boolean;
|
|
5
|
+
requestParams: RequestParams<T>;
|
|
6
|
+
gPage?: Page<T>;
|
|
7
|
+
error?: string | null;
|
|
8
|
+
selectedItems?: T[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const INITIAL_TABLE_STATE: TableState<any> = {
|
|
12
|
+
gPage: {
|
|
13
|
+
data: [],
|
|
14
|
+
size: DEFAULT_SEARCH_LIMIT,
|
|
15
|
+
total: 0.
|
|
16
|
+
} as unknown as Page<any>,
|
|
17
|
+
dataLoading: false,
|
|
18
|
+
requestParams: DEFAULT_SEARCH_PARAMS,
|
|
19
|
+
selectedItems: []
|
|
20
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { ActionCreator, ReducerTypes, createReducer, on } from '@ngrx/store';
|
|
3
|
+
import {
|
|
4
|
+
createGetPageTableAction,
|
|
5
|
+
createStaticFilterTableAction,
|
|
6
|
+
createDataLoadSuccessTableAction,
|
|
7
|
+
createDataLoadErrorTableAction,
|
|
8
|
+
createFilterPageTableAction,
|
|
9
|
+
createDestroyTableAction,
|
|
10
|
+
createSelectItemsTableAction,
|
|
11
|
+
createChangePageSizeTableAction,
|
|
12
|
+
createResetTableFilterAction,
|
|
13
|
+
} from './actions';
|
|
14
|
+
import { INITIAL_TABLE_STATE, TableState } from './models';
|
|
15
|
+
|
|
16
|
+
export function createTablePageReducers<
|
|
17
|
+
T = any,
|
|
18
|
+
TState extends TableState<T> = TableState<T>
|
|
19
|
+
>(
|
|
20
|
+
tableRepository: string,
|
|
21
|
+
initialState: TState = INITIAL_TABLE_STATE as unknown as TState,
|
|
22
|
+
...ons: ReducerTypes<TState, readonly ActionCreator[]>[]
|
|
23
|
+
) {
|
|
24
|
+
return createReducer(
|
|
25
|
+
initialState,
|
|
26
|
+
on(
|
|
27
|
+
createGetPageTableAction(tableRepository),
|
|
28
|
+
(state, { page, id, filter, sort }) => {
|
|
29
|
+
return {
|
|
30
|
+
...state,
|
|
31
|
+
requestParams: {
|
|
32
|
+
...state.requestParams,
|
|
33
|
+
filter: filter ?? state.requestParams.filter,
|
|
34
|
+
sort: sort ?? state.requestParams.sort,
|
|
35
|
+
id: id ?? state.requestParams.id,
|
|
36
|
+
page,
|
|
37
|
+
},
|
|
38
|
+
dataLoading: true,
|
|
39
|
+
submitted: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
|
|
44
|
+
on(createStaticFilterTableAction(tableRepository), (state, { filter }) => {
|
|
45
|
+
return {
|
|
46
|
+
...state,
|
|
47
|
+
requestParams: {
|
|
48
|
+
...state.requestParams,
|
|
49
|
+
filter: {
|
|
50
|
+
...state.requestParams?.filter,
|
|
51
|
+
filter,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
on(createResetTableFilterAction(tableRepository), (state) => {
|
|
58
|
+
return {
|
|
59
|
+
...state,
|
|
60
|
+
requestParams: {
|
|
61
|
+
...state.requestParams,
|
|
62
|
+
filter: undefined,
|
|
63
|
+
page: 1
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
}),
|
|
67
|
+
|
|
68
|
+
on(
|
|
69
|
+
createDataLoadSuccessTableAction(tableRepository),
|
|
70
|
+
(state, { gPage }) => {
|
|
71
|
+
return {
|
|
72
|
+
...state,
|
|
73
|
+
gPage,
|
|
74
|
+
dataLoading: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
|
|
79
|
+
on(createDataLoadErrorTableAction(tableRepository), (state, { error }) => {
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
error,
|
|
83
|
+
dataLoading: false,
|
|
84
|
+
};
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
on(createFilterPageTableAction(tableRepository), (state, { filter }) => {
|
|
88
|
+
return {
|
|
89
|
+
...state,
|
|
90
|
+
requestParams: {
|
|
91
|
+
...state.requestParams,
|
|
92
|
+
filter: filter,
|
|
93
|
+
page: 1,
|
|
94
|
+
},
|
|
95
|
+
dataLoading: true,
|
|
96
|
+
};
|
|
97
|
+
}),
|
|
98
|
+
|
|
99
|
+
on(createDestroyTableAction(tableRepository), () => {
|
|
100
|
+
return {
|
|
101
|
+
...(initialState as any),
|
|
102
|
+
};
|
|
103
|
+
}),
|
|
104
|
+
|
|
105
|
+
on(
|
|
106
|
+
createSelectItemsTableAction(tableRepository),
|
|
107
|
+
(state, { selectedItems }) => {
|
|
108
|
+
return {
|
|
109
|
+
...state,
|
|
110
|
+
selectedItems,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
on(createChangePageSizeTableAction(tableRepository), (state, { size }) => {
|
|
116
|
+
return {
|
|
117
|
+
...state,
|
|
118
|
+
requestParams: {
|
|
119
|
+
...state.requestParams,
|
|
120
|
+
size,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
...ons
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment
|
|
2
|
+
globalThis.ngJest = {
|
|
3
|
+
testEnvironmentOptions: {
|
|
4
|
+
errorOnUnknownElements: true,
|
|
5
|
+
errorOnUnknownProperties: true,
|
|
6
|
+
},
|
|
7
|
+
};
|
|
8
|
+
import 'jest-preset-angular/setup-jest';
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"useDefineForClassFields": false,
|
|
5
|
+
"forceConsistentCasingInFileNames": true,
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noImplicitOverride": true,
|
|
8
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
9
|
+
"noImplicitReturns": true,
|
|
10
|
+
"noFallthroughCasesInSwitch": true
|
|
11
|
+
},
|
|
12
|
+
"files": [],
|
|
13
|
+
"include": [],
|
|
14
|
+
"references": [
|
|
15
|
+
{
|
|
16
|
+
"path": "./tsconfig.lib.json"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"path": "./tsconfig.spec.json"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"extends": "../../../tsconfig.base.json",
|
|
23
|
+
"angularCompilerOptions": {
|
|
24
|
+
"enableI18nLegacyMessageIdFormat": false,
|
|
25
|
+
"strictInjectionParameters": true,
|
|
26
|
+
"strictInputAccessModifiers": true,
|
|
27
|
+
"strictTemplates": true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../../dist/out-tsc",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": true,
|
|
7
|
+
"inlineSources": true,
|
|
8
|
+
"types": []
|
|
9
|
+
},
|
|
10
|
+
"exclude": [
|
|
11
|
+
"src/**/*.spec.ts",
|
|
12
|
+
"src/test-setup.ts",
|
|
13
|
+
"jest.config.ts",
|
|
14
|
+
"src/**/*.test.ts"
|
|
15
|
+
],
|
|
16
|
+
"include": ["src/**/*.ts"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "../../../dist/out-tsc",
|
|
5
|
+
"module": "commonjs",
|
|
6
|
+
"target": "es2016",
|
|
7
|
+
"types": ["jest", "node"]
|
|
8
|
+
},
|
|
9
|
+
"files": ["src/test-setup.ts"],
|
|
10
|
+
"include": [
|
|
11
|
+
"jest.config.ts",
|
|
12
|
+
"src/**/*.test.ts",
|
|
13
|
+
"src/**/*.spec.ts",
|
|
14
|
+
"src/**/*.d.ts"
|
|
15
|
+
]
|
|
16
|
+
}
|