@verisoft/store 20.0.0 → 20.1.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.
@@ -0,0 +1,89 @@
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 createInitNewDetailAction(detailsRepository: string) {
12
+ return createAction(`[${detailsRepository} Page] Init New`);
13
+ }
14
+
15
+ export function createLoadDetailSuccessAction<T>(detailsRepository: string) {
16
+ return createAction(
17
+ `[${detailsRepository}/API] Load ${detailsRepository} Success`,
18
+ props<{ item: T }>()
19
+ );
20
+ }
21
+
22
+ export function createLoadDetailFailureAction(detailsRepository: string) {
23
+ return createAction(
24
+ `[${detailsRepository}/API] Load ${detailsRepository} Failure`,
25
+ props<{ error: any }>()
26
+ );
27
+ }
28
+
29
+ export function createUpdateDetailAction<T>(detailsRepository: string) {
30
+ return createAction(
31
+ `[${detailsRepository}/API] Update ${detailsRepository}`,
32
+ props<{ item: T }>()
33
+ );
34
+ }
35
+
36
+ export function createUpdateDetailSetErrorsAction(detailsRepository: string) {
37
+ return createAction(
38
+ `[${detailsRepository}/API] Update Validation Errors ${detailsRepository}`,
39
+ props<{ error: any }>()
40
+ )
41
+ }
42
+
43
+ export function createUpdateFormStateAction(detailsRepository: string) {
44
+ return createAction(
45
+ `[${detailsRepository}/API] Update Form State`,
46
+ props<{ formState: FormState }>()
47
+ );
48
+ }
49
+
50
+ export function createSaveDetailAction(detailsRepository: string) {
51
+ return createAction(`[${detailsRepository}/API] Save Detail `);
52
+ }
53
+
54
+ export function createSaveDetailSuccessAction<T>(detailsRepository: string) {
55
+ return createAction(
56
+ `[${detailsRepository}/API] Save Detail Success`,
57
+ props<{ item: T }>()
58
+ );
59
+ }
60
+
61
+ export function createSaveDetailFailureAction(detailsRepository: string) {
62
+ return createAction(
63
+ `[${detailsRepository}/API] Save Detail Failure`,
64
+ props<{ error: any }>()
65
+ );
66
+ }
67
+
68
+ export function createUpdateSaveDetailAction(detailsRepository: string) {
69
+ return createAction(`[${detailsRepository}/API] Update Detail `);
70
+ }
71
+
72
+ export function createResetStateAction(detailsRepository: string) {
73
+ return createAction(`[${detailsRepository}/API] Reset State`);
74
+ }
75
+
76
+ export function createDeleteDetailAction(detailsRepository: string) {
77
+ return createAction(`[${detailsRepository}/API] Delete Detail`);
78
+ }
79
+
80
+ export function createDeleteDetailSuccessAction(detailsRepository: string) {
81
+ return createAction(`[${detailsRepository}/API] Delete Detail Success`);
82
+ }
83
+
84
+ export function createDeleteDetailFailureAction(detailsRepository: string) {
85
+ return createAction(
86
+ `[${detailsRepository}/API] Delete Detail Failure`,
87
+ props<{ error: any }>()
88
+ );
89
+ }
@@ -0,0 +1,283 @@
1
+ import { Router } from '@angular/router';
2
+ import { Actions, createEffect, ofType } from '@ngrx/effects';
3
+ import { BaseHttpService } from '@verisoft/core';
4
+ import {
5
+ catchError,
6
+ EMPTY,
7
+ map,
8
+ Observable,
9
+ of,
10
+ switchMap,
11
+ tap,
12
+ withLatestFrom,
13
+ } from 'rxjs';
14
+ import {
15
+ createDeleteDetailAction,
16
+ createDeleteDetailFailureAction,
17
+ createDeleteDetailSuccessAction,
18
+ createInitDetailAction,
19
+ createInitNewDetailAction,
20
+ createLoadDetailFailureAction,
21
+ createLoadDetailSuccessAction,
22
+ createSaveDetailAction,
23
+ createSaveDetailFailureAction,
24
+ createSaveDetailSuccessAction,
25
+ } from './detail.actions';
26
+
27
+ export interface CreateInitFormConfig<T> {
28
+ service?: BaseHttpService<T>;
29
+ get?: (obj: any) => Observable<T>;
30
+ snackbar?: any;
31
+ errorMessage?: string;
32
+ notFoundMessage?: string;
33
+ successMessge?: string;
34
+ }
35
+
36
+ export interface DetailEffectConfig<T> {
37
+ detail$: Observable<T | undefined>;
38
+ snackbar?: any;
39
+ router?: Router;
40
+ redirectPath?: string;
41
+ useItemId?: boolean;
42
+ navigationMap?: Map<keyof T, string>;
43
+ }
44
+
45
+ export function createInitDetailEffect<T>(
46
+ detailsRepository: string,
47
+ actions$: Actions,
48
+ config: CreateInitFormConfig<T>
49
+ ) {
50
+ return createEffect(() => {
51
+ return actions$.pipe(
52
+ ofType(createInitDetailAction(detailsRepository)),
53
+ switchMap(({ obj }) => {
54
+ const get$: Observable<any> = (config?.service?.get(obj as any) ??
55
+ config?.get?.(obj)) as Observable<any>;
56
+ if (!get$) return EMPTY;
57
+ return get$?.pipe(
58
+ map((item) => {
59
+ return createLoadDetailSuccessAction(detailsRepository)({ item });
60
+ }),
61
+ catchError((error) => {
62
+ if (error.status === 404) {
63
+ config?.snackbar?.showError(config?.notFoundMessage ?? error);
64
+ } else {
65
+ config?.snackbar?.showError(config?.errorMessage ?? error);
66
+ }
67
+
68
+ console.error('Error', error);
69
+ return of(
70
+ createLoadDetailFailureAction(detailsRepository)({ error })
71
+ );
72
+ })
73
+ );
74
+ })
75
+ );
76
+ });
77
+ }
78
+
79
+ export function createInitNewDetailEffect(
80
+ detailsRepository: string,
81
+ actions$: Actions
82
+ ) {
83
+ return createEffect(() =>
84
+ actions$.pipe(
85
+ ofType(createInitNewDetailAction(detailsRepository)),
86
+ map(() => {
87
+ return createLoadDetailSuccessAction(detailsRepository)({ item: null });
88
+ })
89
+ )
90
+ );
91
+ }
92
+
93
+ export function createSaveDetailEffect<
94
+ T extends { id: number | string },
95
+ SERVICE extends BaseHttpService<T>
96
+ >(
97
+ detailsRepository: string,
98
+ actions$: Actions,
99
+ service: SERVICE,
100
+ config: DetailEffectConfig<T>
101
+ ) {
102
+ return createEffect(() =>
103
+ actions$.pipe(
104
+ ofType(createSaveDetailAction(detailsRepository)),
105
+ withLatestFrom(config.detail$),
106
+ switchMap(([, savedItem]) => {
107
+ const isNew = !savedItem?.id;
108
+ const saveAction = isNew
109
+ ? service.post(savedItem as T)
110
+ : service.put(savedItem.id, savedItem);
111
+ return saveAction.pipe(
112
+ map((item) => {
113
+ if (isNew && config?.router && config?.redirectPath) {
114
+ const suffix = config?.useItemId ? '/' + item.id : '';
115
+ config.router.navigateByUrl(config.redirectPath + suffix);
116
+ }
117
+
118
+ if (isNew && config?.router && config?.navigationMap) {
119
+ const url = constructUrlFromNavigationMap(item, config.navigationMap)
120
+ config.router.navigateByUrl(url);
121
+ }
122
+
123
+ return createSaveDetailSuccessAction(detailsRepository)({
124
+ item: item,
125
+ });
126
+ }),
127
+ catchError((error) => {
128
+ console.error('Error', error);
129
+ return of(
130
+ createSaveDetailFailureAction(detailsRepository)({ error })
131
+ );
132
+ })
133
+ );
134
+ })
135
+ )
136
+ );
137
+ }
138
+
139
+ export function createSaveDetailSuccessEffect(
140
+ detailsRepository: string,
141
+ actions$: Actions,
142
+ config?: CreateInitFormConfig<unknown>
143
+ ) {
144
+ return createEffect(
145
+ () =>
146
+ actions$.pipe(
147
+ ofType(createSaveDetailSuccessAction(detailsRepository)),
148
+ map((data: any) => {
149
+ config?.snackbar?.showSuccess(
150
+ config?.successMessge ?? `Item successfully saved.`
151
+ );
152
+ return createInitDetailAction(detailsRepository)({ obj: data.item.id });
153
+ })
154
+ )
155
+ );
156
+ }
157
+
158
+ export function createSaveDetailFailureEffect(
159
+ detailsRepository: string,
160
+ actions$: Actions,
161
+ config?: CreateInitFormConfig<unknown>
162
+ ) {
163
+ return createEffect(
164
+ () =>
165
+ actions$.pipe(
166
+ ofType(createSaveDetailFailureAction(detailsRepository)),
167
+ tap(({ error }) => {
168
+ config?.snackbar?.showError(
169
+ config.errorMessage ?? `Error saving item. ${error.message}`
170
+ );
171
+ })
172
+ ),
173
+ {
174
+ dispatch: false,
175
+ }
176
+ );
177
+ }
178
+
179
+ export function createDeleteDetailEffect<
180
+ T extends { id: number | string },
181
+ SERVICE extends BaseHttpService<T>
182
+ >(
183
+ detailsRepository: string,
184
+ actions$: Actions,
185
+ service: SERVICE,
186
+ config: DetailEffectConfig<T>,
187
+ ) {
188
+ return createEffect(() =>
189
+ actions$.pipe(
190
+ ofType(createDeleteDetailAction(detailsRepository)),
191
+ withLatestFrom(config.detail$),
192
+ switchMap(([, savedItem]) => {
193
+ const id = savedItem?.id;
194
+ if (!id) {
195
+ return of(
196
+ createSaveDetailFailureAction(detailsRepository)({
197
+ error: 'Item has no id',
198
+ })
199
+ );
200
+ }
201
+
202
+ return service.delete(id).pipe(
203
+ map(() => {
204
+ if (config?.router && config?.redirectPath) {
205
+ config.router.navigateByUrl(`${config.redirectPath}`);
206
+ }
207
+
208
+ if (config?.router && config?.navigationMap) {
209
+ const url = constructUrlFromNavigationMap(savedItem, config.navigationMap)
210
+ config.router.navigateByUrl(url);
211
+ }
212
+
213
+ return createDeleteDetailSuccessAction(detailsRepository)();
214
+ }),
215
+ catchError((error) => {
216
+ console.error('Error', error);
217
+ config.snackbar?.showError('Failed to delete item.');
218
+ return of(
219
+ createDeleteDetailFailureAction(detailsRepository)({ error })
220
+ );
221
+ })
222
+ );
223
+ })
224
+ )
225
+ );
226
+ }
227
+
228
+ export function createDeleteDetailSuccessEffect(
229
+ detailsRepository: string,
230
+ actions$: Actions,
231
+ config?: CreateInitFormConfig<unknown>
232
+ ) {
233
+ return createEffect(
234
+ () =>
235
+ actions$.pipe(
236
+ ofType(createDeleteDetailSuccessAction(detailsRepository)),
237
+ tap(() => {
238
+ config?.snackbar?.showSuccess(
239
+ config.successMessge ?? `Item successfully deleted.`
240
+ );
241
+ })
242
+ ),
243
+ {
244
+ dispatch: false,
245
+ }
246
+ );
247
+ }
248
+
249
+ export function createDeleteDetailFailureEffect(
250
+ detailsRepository: string,
251
+ actions$: Actions,
252
+ config?: CreateInitFormConfig<unknown>
253
+ ) {
254
+ return createEffect(
255
+ () =>
256
+ actions$.pipe(
257
+ ofType(createDeleteDetailFailureAction(detailsRepository)),
258
+ tap(({ error }) => {
259
+ config?.snackbar?.showError(
260
+ config.errorMessage ?? `Error deleting item. ${error.message}`
261
+ );
262
+ })
263
+ ),
264
+ {
265
+ dispatch: false,
266
+ }
267
+ );
268
+ }
269
+
270
+ function constructUrlFromNavigationMap<T extends object>(
271
+ item: T,
272
+ navigationMap: Map<keyof T, string>)
273
+ : string {
274
+ let url = '';
275
+ navigationMap.forEach((urlPartValue, suffixPropertyKey) => {
276
+ const suffix = item[suffixPropertyKey];
277
+ if (suffix) {
278
+ url += `${urlPartValue}/${suffix}`;
279
+ }
280
+ });
281
+
282
+ return url;
283
+ }
@@ -0,0 +1,42 @@
1
+ export interface DetailState<T> {
2
+ loaded: boolean;
3
+ item?: T;
4
+ formState?: FormState;
5
+ error?: string | null;
6
+ saveItemState: SaveItemState;
7
+ backendValidationErrors: BackendValidationError[];
8
+ }
9
+
10
+ export interface BackendValidationError {
11
+ code: string;
12
+ message: string;
13
+ parameters: string;
14
+ }
15
+
16
+ export interface ChosenRowsState<T> {
17
+ rows: Array<T>;
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 const INITIAL_CHOSEN_ROW_STATE: ChosenRowsState<any> = {
30
+ rows: []
31
+ }
32
+
33
+ export const INITIAL_DETAIL_STATE: DetailState<any> = {
34
+ loaded: false,
35
+ saveItemState: INITIAL_SAVE_ITEM_STATE,
36
+ backendValidationErrors: [],
37
+ };
38
+
39
+ export interface FormState {
40
+ dirty: boolean;
41
+ valid: boolean;
42
+ }
@@ -0,0 +1,147 @@
1
+ import { ActionCreator, createReducer, on, ReducerTypes } from '@ngrx/store';
2
+ import {
3
+ createInitDetailAction,
4
+ createInitNewDetailAction,
5
+ createLoadDetailFailureAction,
6
+ createLoadDetailSuccessAction,
7
+ createResetStateAction,
8
+ createSaveDetailAction,
9
+ createSaveDetailFailureAction,
10
+ createSaveDetailSuccessAction,
11
+ createUpdateDetailAction,
12
+ createUpdateDetailSetErrorsAction,
13
+ createUpdateFormStateAction,
14
+ } from './detail.actions';
15
+ import {
16
+ DetailState,
17
+ INITIAL_DETAIL_STATE,
18
+ INITIAL_SAVE_ITEM_STATE,
19
+ } from './detail.models';
20
+
21
+ export function createDetailReducers<
22
+ T = any,
23
+ TState extends DetailState<T> = DetailState<T>
24
+ >(
25
+ detailRepository: string,
26
+ initialState: TState = INITIAL_DETAIL_STATE as unknown as TState,
27
+ ...ons: ReducerTypes<TState, readonly ActionCreator[]>[]
28
+ ) {
29
+ return createReducer(
30
+ initialState,
31
+ on(createInitDetailAction(detailRepository), (state) => {
32
+ return {
33
+ ...state,
34
+ loaded: false,
35
+ error: null,
36
+ };
37
+ }),
38
+
39
+ on(createInitNewDetailAction(detailRepository), (state) => {
40
+ return {
41
+ ...state,
42
+ loaded: false,
43
+ error: null,
44
+ };
45
+ }),
46
+
47
+ on(createLoadDetailSuccessAction(detailRepository), (state, { item }) => {
48
+ return {
49
+ ...state,
50
+ loaded: true,
51
+ item,
52
+ };
53
+ }),
54
+
55
+ on(createLoadDetailFailureAction(detailRepository), (state, { error }) => {
56
+ return {
57
+ ...state,
58
+ loaded: true,
59
+ error,
60
+ };
61
+ }),
62
+
63
+ on(createUpdateDetailAction(detailRepository), (state, { item }) => {
64
+ return {
65
+ ...state,
66
+ item,
67
+ formState: {
68
+ dirty: true,
69
+ valid: state.formState?.valid ?? true,
70
+ },
71
+ };
72
+ }),
73
+
74
+ on(createUpdateDetailSetErrorsAction(detailRepository), (state, { error }) => {
75
+ return {
76
+ ...state,
77
+ backendValidationErrors: error
78
+ }
79
+ }),
80
+
81
+ on(
82
+ createUpdateFormStateAction(detailRepository),
83
+ (state, { formState }) => {
84
+ return {
85
+ ...state,
86
+ formState,
87
+ };
88
+ }
89
+ ),
90
+
91
+ on(createSaveDetailSuccessAction(detailRepository), (state) => {
92
+ return {
93
+ ...state,
94
+ formState: {
95
+ dirty: false,
96
+ valid: state.formState?.valid ?? true,
97
+ },
98
+ };
99
+ }),
100
+
101
+ on(createResetStateAction(detailRepository), () => {
102
+ return {
103
+ ...(initialState as any),
104
+ };
105
+ }),
106
+
107
+ on(createSaveDetailFailureAction(detailRepository), (state, { error }) => {
108
+ return {
109
+ ...state,
110
+ backendValidationErrors: error.error
111
+ }
112
+ }),
113
+ ...ons
114
+ );
115
+ }
116
+
117
+ export function createSaveDetailReducers(
118
+ detailRepository: string,
119
+ initialState = INITIAL_SAVE_ITEM_STATE
120
+ ) {
121
+ return createReducer(
122
+ initialState,
123
+
124
+ on(createSaveDetailAction(detailRepository), (state) => {
125
+ return {
126
+ ...state,
127
+ saveInProgress: true,
128
+ };
129
+ }),
130
+
131
+ on(createSaveDetailSuccessAction(detailRepository), (state, { item }) => {
132
+ return {
133
+ ...state,
134
+ item,
135
+ saveInProgress: false,
136
+ };
137
+ }),
138
+
139
+ on(createSaveDetailFailureAction(detailRepository), (state, { error }) => {
140
+ return {
141
+ ...state,
142
+ error,
143
+ saveInProgress: false,
144
+ };
145
+ })
146
+ );
147
+ }
@@ -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[];
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
+ }