@smartsoft001/crud-shell-angular 2.76.0 → 2.80.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/fesm2022/smartsoft001-crud-shell-angular.mjs +2840 -0
- package/fesm2022/smartsoft001-crud-shell-angular.mjs.map +1 -0
- package/index.d.ts +632 -0
- package/package.json +18 -3
- package/.storybook/addons.js +0 -1
- package/.storybook/config.js +0 -5
- package/.storybook/tsconfig.json +0 -8
- package/.storybook/webpack.config.js +0 -7
- package/jest.config.ts +0 -38
- package/ng-package.json +0 -7
- package/project.json +0 -49
- package/src/index.ts +0 -13
- package/src/lib/+state/crud.facade.ts +0 -82
- package/src/lib/+state/crud.methods.ts +0 -258
- package/src/lib/+state/crud.store.ts +0 -77
- package/src/lib/components/components.module.ts +0 -58
- package/src/lib/components/export/export.component.ts +0 -69
- package/src/lib/components/filter/base/base.component.ts +0 -175
- package/src/lib/components/filter/check/check.component.scss +0 -25
- package/src/lib/components/filter/check/check.component.ts +0 -72
- package/src/lib/components/filter/date/date.component.html +0 -30
- package/src/lib/components/filter/date/date.component.ts +0 -70
- package/src/lib/components/filter/date-time/date-time.component.html +0 -62
- package/src/lib/components/filter/date-time/date-time.component.ts +0 -56
- package/src/lib/components/filter/date-with-edit/date-with-edit.component.html +0 -109
- package/src/lib/components/filter/date-with-edit/date-with-edit.component.ts +0 -70
- package/src/lib/components/filter/filter.component.html +0 -52
- package/src/lib/components/filter/filter.component.ts +0 -34
- package/src/lib/components/filter/flag/flag.component.ts +0 -55
- package/src/lib/components/filter/index.ts +0 -9
- package/src/lib/components/filter/int/int.component.html +0 -74
- package/src/lib/components/filter/int/int.component.ts +0 -57
- package/src/lib/components/filter/radio/radio.component.ts +0 -74
- package/src/lib/components/filter/text/text.component.ts +0 -46
- package/src/lib/components/filters/filters.component.scss +0 -150
- package/src/lib/components/filters/filters.component.ts +0 -157
- package/src/lib/components/filters-config/filters-config.component.ts +0 -69
- package/src/lib/components/group/group.component.ts +0 -93
- package/src/lib/components/index.ts +0 -7
- package/src/lib/components/multiselect/multiselect.component.html +0 -41
- package/src/lib/components/multiselect/multiselect.component.scss +0 -125
- package/src/lib/components/multiselect/multiselect.component.ts +0 -104
- package/src/lib/crud-full.module.ts +0 -51
- package/src/lib/crud.config.ts +0 -79
- package/src/lib/crud.module.ts +0 -72
- package/src/lib/factories/list-pagination/list-pagination.factory.ts +0 -77
- package/src/lib/models/index.ts +0 -1
- package/src/lib/models/interfaces.ts +0 -34
- package/src/lib/pages/index.ts +0 -6
- package/src/lib/pages/item/base/base.component.ts +0 -54
- package/src/lib/pages/item/item.component.ts +0 -513
- package/src/lib/pages/item/standard/standard.component.scss +0 -0
- package/src/lib/pages/item/standard/standard.component.ts +0 -50
- package/src/lib/pages/list/base/base.component.ts +0 -29
- package/src/lib/pages/list/list.component.stories.ts +0 -77
- package/src/lib/pages/list/list.component.ts +0 -438
- package/src/lib/pages/list/standard/standard.component.ts +0 -42
- package/src/lib/pipes/form-options/form-options.pipe.ts +0 -61
- package/src/lib/pipes/index.ts +0 -2
- package/src/lib/pipes/pipes.module.ts +0 -9
- package/src/lib/providers/model-possibilities/model-possibilities.provider.ts +0 -19
- package/src/lib/services/crud/crud.service.spec.ts +0 -303
- package/src/lib/services/crud/crud.service.ts +0 -178
- package/src/lib/services/list-group/list-group.service.spec.ts +0 -87
- package/src/lib/services/list-group/list-group.service.ts +0 -65
- package/src/lib/services/page/page.service.spec.ts +0 -143
- package/src/lib/services/page/page.service.ts +0 -44
- package/src/lib/services/search/search.service.spec.ts +0 -134
- package/src/lib/services/search/search.service.ts +0 -42
- package/test-setup.ts +0 -0
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -31
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -10
- package/tslint.json +0 -10
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
import '@angular/compiler';
|
|
2
|
-
import 'zone.js';
|
|
3
|
-
import 'zone.js/testing';
|
|
4
|
-
import { HttpClient } from '@angular/common/http';
|
|
5
|
-
import {
|
|
6
|
-
HttpTestingController,
|
|
7
|
-
HttpClientTestingModule,
|
|
8
|
-
} from '@angular/common/http/testing';
|
|
9
|
-
import { TestBed } from '@angular/core/testing';
|
|
10
|
-
import {
|
|
11
|
-
BrowserDynamicTestingModule,
|
|
12
|
-
platformBrowserDynamicTesting,
|
|
13
|
-
} from '@angular/platform-browser-dynamic/testing';
|
|
14
|
-
|
|
15
|
-
import { IEntity } from '@smartsoft001/domain-core';
|
|
16
|
-
|
|
17
|
-
import { CrudService } from './crud.service';
|
|
18
|
-
import { CrudConfig } from '../../crud.config';
|
|
19
|
-
import { ICrudCreateManyOptions, ICrudFilter } from '../../models';
|
|
20
|
-
|
|
21
|
-
interface TestEntity extends IEntity<string> {
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
describe('crud-shell-angular: CrudService', () => {
|
|
27
|
-
let service: CrudService<TestEntity>;
|
|
28
|
-
let httpMock: HttpTestingController;
|
|
29
|
-
let httpClient: HttpClient;
|
|
30
|
-
let config: CrudConfig<TestEntity>;
|
|
31
|
-
|
|
32
|
-
// Mock URL.createObjectURL globally before tests
|
|
33
|
-
const mockCreateObjectURL = jest.fn().mockReturnValue('blob-url');
|
|
34
|
-
global.URL.createObjectURL = mockCreateObjectURL;
|
|
35
|
-
|
|
36
|
-
TestBed.initTestEnvironment(
|
|
37
|
-
BrowserDynamicTestingModule,
|
|
38
|
-
platformBrowserDynamicTesting(),
|
|
39
|
-
{
|
|
40
|
-
teardown: { destroyAfterEach: true },
|
|
41
|
-
},
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
beforeEach(() => {
|
|
45
|
-
config = new CrudConfig<TestEntity>();
|
|
46
|
-
config.entity = '';
|
|
47
|
-
config.apiUrl = 'http://api.test/entities';
|
|
48
|
-
|
|
49
|
-
TestBed.configureTestingModule({
|
|
50
|
-
imports: [HttpClientTestingModule],
|
|
51
|
-
providers: [
|
|
52
|
-
{
|
|
53
|
-
provide: CrudConfig,
|
|
54
|
-
useValue: config,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
provide: CrudService,
|
|
58
|
-
useFactory: () => new CrudService<TestEntity>(),
|
|
59
|
-
},
|
|
60
|
-
],
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
service = TestBed.inject(CrudService);
|
|
64
|
-
httpMock = TestBed.inject(HttpTestingController);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
afterEach(() => {
|
|
68
|
-
httpMock.verify();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('create', () => {
|
|
72
|
-
it('should create an entity and return id from location header', async () => {
|
|
73
|
-
const testEntity: TestEntity = { id: '', name: 'Test Entity' };
|
|
74
|
-
|
|
75
|
-
service.create(testEntity);
|
|
76
|
-
|
|
77
|
-
const req = httpMock.expectOne(config.apiUrl);
|
|
78
|
-
expect(req.request.method).toBe('POST');
|
|
79
|
-
expect(req.request.body).toEqual(testEntity);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('should return null when location header is missing', async () => {
|
|
83
|
-
const testEntity: TestEntity = { id: '', name: 'Test Entity' };
|
|
84
|
-
|
|
85
|
-
const promise = service.create(testEntity);
|
|
86
|
-
|
|
87
|
-
const req = httpMock.expectOne(config.apiUrl);
|
|
88
|
-
req.flush(null, { headers: {} });
|
|
89
|
-
|
|
90
|
-
const result = await promise;
|
|
91
|
-
expect(result).toBeNull();
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('createMany', () => {
|
|
96
|
-
it('should create multiple entities', async () => {
|
|
97
|
-
const testEntities: TestEntity[] = [
|
|
98
|
-
{ id: '', name: 'Entity 1' },
|
|
99
|
-
{ id: '', name: 'Entity 2' },
|
|
100
|
-
];
|
|
101
|
-
const options: ICrudCreateManyOptions = { mode: 'default' };
|
|
102
|
-
|
|
103
|
-
const promise = service.createMany(testEntities, options);
|
|
104
|
-
|
|
105
|
-
const req = httpMock.expectOne(`${config.apiUrl}/bulk?mode=default`);
|
|
106
|
-
expect(req.request.method).toBe('POST');
|
|
107
|
-
expect(req.request.body).toEqual(testEntities);
|
|
108
|
-
|
|
109
|
-
// req.flush(null);
|
|
110
|
-
//
|
|
111
|
-
// await promise;
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('getById', () => {
|
|
116
|
-
it('should fetch entity by id', async () => {
|
|
117
|
-
const testId = '123';
|
|
118
|
-
const expectedEntity: TestEntity = { id: testId, name: 'Test Entity' };
|
|
119
|
-
|
|
120
|
-
const promise = service.getById(testId);
|
|
121
|
-
|
|
122
|
-
const req = httpMock.expectOne(`${config.apiUrl}/${testId}`);
|
|
123
|
-
expect(req.request.method).toBe('GET');
|
|
124
|
-
|
|
125
|
-
req.flush(expectedEntity);
|
|
126
|
-
|
|
127
|
-
const result = await promise;
|
|
128
|
-
expect(result).toEqual(expectedEntity);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
describe('getList', () => {
|
|
133
|
-
it('should fetch list of entities without filter', async () => {
|
|
134
|
-
const expectedResponse = {
|
|
135
|
-
data: [{ id: '1', name: 'Entity 1' }],
|
|
136
|
-
totalCount: 1,
|
|
137
|
-
links: {},
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const promise = service.getList();
|
|
141
|
-
|
|
142
|
-
const req = httpMock.expectOne(config.apiUrl);
|
|
143
|
-
expect(req.request.method).toBe('GET');
|
|
144
|
-
|
|
145
|
-
req.flush(expectedResponse);
|
|
146
|
-
|
|
147
|
-
const result = await promise;
|
|
148
|
-
expect(result).toEqual(expectedResponse);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('should fetch list of entities with search filter', async () => {
|
|
152
|
-
const filter: ICrudFilter = { searchText: 'test' };
|
|
153
|
-
const expectedResponse = {
|
|
154
|
-
data: [{ id: '1', name: 'Test Entity' }],
|
|
155
|
-
totalCount: 1,
|
|
156
|
-
links: {},
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const promise = service.getList(filter);
|
|
160
|
-
|
|
161
|
-
const req = httpMock.expectOne(`${config.apiUrl}?$search=test`);
|
|
162
|
-
expect(req.request.method).toBe('GET');
|
|
163
|
-
|
|
164
|
-
req.flush(expectedResponse);
|
|
165
|
-
|
|
166
|
-
const result = await promise;
|
|
167
|
-
expect(result).toEqual(expectedResponse);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
describe('update', () => {
|
|
172
|
-
it('should update an entity', async () => {
|
|
173
|
-
const testEntity: TestEntity = { id: '123', name: 'Updated Entity' };
|
|
174
|
-
|
|
175
|
-
const promise = service.update(testEntity);
|
|
176
|
-
|
|
177
|
-
const req = httpMock.expectOne(`${config.apiUrl}/${testEntity.id}`);
|
|
178
|
-
expect(req.request.method).toBe('PUT');
|
|
179
|
-
expect(req.request.body).toEqual(testEntity);
|
|
180
|
-
|
|
181
|
-
req.flush(null);
|
|
182
|
-
|
|
183
|
-
await promise;
|
|
184
|
-
});
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
describe('updatePartial', () => {
|
|
188
|
-
it('should partially update an entity', async () => {
|
|
189
|
-
const partialUpdate = { id: '123', name: 'Partially Updated' };
|
|
190
|
-
|
|
191
|
-
const promise = service.updatePartial(partialUpdate);
|
|
192
|
-
|
|
193
|
-
const req = httpMock.expectOne(`${config.apiUrl}/${partialUpdate.id}`);
|
|
194
|
-
expect(req.request.method).toBe('PATCH');
|
|
195
|
-
expect(req.request.body).toEqual(partialUpdate);
|
|
196
|
-
|
|
197
|
-
req.flush(null);
|
|
198
|
-
|
|
199
|
-
await promise;
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
describe('updatePartialMany', () => {
|
|
204
|
-
it('should update multiple entities partially', async () => {
|
|
205
|
-
const updates = [
|
|
206
|
-
{ id: '1', name: 'Update 1' },
|
|
207
|
-
{ id: '2', name: 'Update 2' },
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
const promise = service.updatePartialMany(updates);
|
|
211
|
-
|
|
212
|
-
updates.forEach((update) => {
|
|
213
|
-
const req = httpMock.expectOne(`${config.apiUrl}/${update.id}`);
|
|
214
|
-
expect(req.request.method).toBe('PATCH');
|
|
215
|
-
expect(req.request.body).toEqual(update);
|
|
216
|
-
req.flush(null);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
await promise;
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
describe('delete', () => {
|
|
224
|
-
it('should delete an entity', async () => {
|
|
225
|
-
const testId = '123';
|
|
226
|
-
|
|
227
|
-
const promise = service.delete(testId);
|
|
228
|
-
|
|
229
|
-
const req = httpMock.expectOne(`${config.apiUrl}/${testId}`);
|
|
230
|
-
expect(req.request.method).toBe('DELETE');
|
|
231
|
-
|
|
232
|
-
req.flush(null);
|
|
233
|
-
|
|
234
|
-
await promise;
|
|
235
|
-
});
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
describe('exportList', () => {
|
|
239
|
-
beforeEach(() => {
|
|
240
|
-
// Reset the mock before each test
|
|
241
|
-
mockCreateObjectURL.mockClear();
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('should export list as CSV', async () => {
|
|
245
|
-
const format = 'csv';
|
|
246
|
-
const csvData = 'id,name\n1,Test';
|
|
247
|
-
|
|
248
|
-
// Mock document.createElement
|
|
249
|
-
const mockLink = {
|
|
250
|
-
href: '',
|
|
251
|
-
download: '',
|
|
252
|
-
click: jest.fn(),
|
|
253
|
-
};
|
|
254
|
-
jest.spyOn(document, 'createElement').mockReturnValue(mockLink as any);
|
|
255
|
-
jest.spyOn(document.body, 'appendChild').mockImplementation();
|
|
256
|
-
jest.spyOn(document.body, 'removeChild').mockImplementation();
|
|
257
|
-
|
|
258
|
-
const promise = service.exportList(null, format);
|
|
259
|
-
|
|
260
|
-
const req = httpMock.expectOne(config.apiUrl);
|
|
261
|
-
expect(req.request.method).toBe('GET');
|
|
262
|
-
expect(req.request.headers.get('Content-Type')).toBe('text/csv');
|
|
263
|
-
|
|
264
|
-
req.flush(csvData);
|
|
265
|
-
|
|
266
|
-
await promise;
|
|
267
|
-
|
|
268
|
-
expect(mockCreateObjectURL).toHaveBeenCalled();
|
|
269
|
-
expect(mockLink.download).toBe('data.csv');
|
|
270
|
-
expect(mockLink.click).toHaveBeenCalled();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
it('should export list as XLSX', async () => {
|
|
274
|
-
const format = 'xlsx';
|
|
275
|
-
const xlsxData = new Blob(['fake xlsx data']);
|
|
276
|
-
|
|
277
|
-
const mockLink = {
|
|
278
|
-
href: '',
|
|
279
|
-
download: '',
|
|
280
|
-
click: jest.fn(),
|
|
281
|
-
};
|
|
282
|
-
jest.spyOn(document, 'createElement').mockReturnValue(mockLink as any);
|
|
283
|
-
jest.spyOn(document.body, 'appendChild').mockImplementation();
|
|
284
|
-
jest.spyOn(document.body, 'removeChild').mockImplementation();
|
|
285
|
-
|
|
286
|
-
const promise = service.exportList(null, format);
|
|
287
|
-
|
|
288
|
-
const req = httpMock.expectOne(config.apiUrl);
|
|
289
|
-
expect(req.request.method).toBe('GET');
|
|
290
|
-
expect(req.request.headers.get('Content-Type')).toBe(
|
|
291
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
req.flush(xlsxData);
|
|
295
|
-
|
|
296
|
-
await promise;
|
|
297
|
-
|
|
298
|
-
expect(mockCreateObjectURL).toHaveBeenCalled();
|
|
299
|
-
expect(mockLink.download).toBe('data.xlsx');
|
|
300
|
-
expect(mockLink.click).toHaveBeenCalled();
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
});
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { HttpClient } from '@angular/common/http';
|
|
2
|
-
import { inject, Injectable } from '@angular/core';
|
|
3
|
-
import { firstValueFrom } from 'rxjs';
|
|
4
|
-
import { map } from 'rxjs/operators';
|
|
5
|
-
|
|
6
|
-
import { IEntity } from '@smartsoft001/domain-core';
|
|
7
|
-
|
|
8
|
-
import { CrudConfig } from '../../crud.config';
|
|
9
|
-
import { ICrudCreateManyOptions, ICrudFilter } from '../../models';
|
|
10
|
-
|
|
11
|
-
@Injectable()
|
|
12
|
-
export class CrudService<T extends IEntity<string>> {
|
|
13
|
-
protected config = inject(CrudConfig<T>);
|
|
14
|
-
protected http = inject(HttpClient);
|
|
15
|
-
|
|
16
|
-
protected _formatMap = {
|
|
17
|
-
csv: 'text/csv',
|
|
18
|
-
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
create(item: T): Promise<string> {
|
|
22
|
-
return firstValueFrom(
|
|
23
|
-
this.http
|
|
24
|
-
.post<void>(this.config.apiUrl, item, { observe: 'response' })
|
|
25
|
-
.pipe(
|
|
26
|
-
map((response) => {
|
|
27
|
-
const location = response.headers['Location'];
|
|
28
|
-
if (!location) return null;
|
|
29
|
-
const array = location.split('/');
|
|
30
|
-
return array[array.length - 1];
|
|
31
|
-
}),
|
|
32
|
-
),
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
createMany(items: Array<T>, options: ICrudCreateManyOptions): Promise<void> {
|
|
37
|
-
return firstValueFrom(
|
|
38
|
-
this.http.post<void>(
|
|
39
|
-
this.config.apiUrl + '/bulk?mode=' + options.mode,
|
|
40
|
-
items,
|
|
41
|
-
),
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
getById(id: string): Promise<T> {
|
|
46
|
-
return firstValueFrom(this.http.get<T>(this.config.apiUrl + '/' + id));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
getList<T>(
|
|
50
|
-
filter: ICrudFilter = null,
|
|
51
|
-
): Promise<{ data: T[]; totalCount: number; links }> {
|
|
52
|
-
return firstValueFrom(
|
|
53
|
-
this.http.get<{ data: T[]; totalCount: number; links }>(
|
|
54
|
-
this.config.apiUrl + this.getQuery(filter),
|
|
55
|
-
),
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
exportList(
|
|
60
|
-
filter: ICrudFilter = null,
|
|
61
|
-
format: 'csv' | 'xlsx',
|
|
62
|
-
): Promise<void> {
|
|
63
|
-
if (!format) {
|
|
64
|
-
format = 'csv';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (format === 'xlsx') {
|
|
68
|
-
return firstValueFrom(
|
|
69
|
-
this.http
|
|
70
|
-
.get(this.config.apiUrl + this.getQuery(filter), {
|
|
71
|
-
headers: {
|
|
72
|
-
'Content-Type': this._formatMap[format],
|
|
73
|
-
},
|
|
74
|
-
observe: 'response',
|
|
75
|
-
reportProgress: false,
|
|
76
|
-
responseType: 'blob',
|
|
77
|
-
})
|
|
78
|
-
.pipe(
|
|
79
|
-
map((res) => {
|
|
80
|
-
const downloadLink = document.createElement('a');
|
|
81
|
-
const blob = res.body;
|
|
82
|
-
|
|
83
|
-
downloadLink.href = URL.createObjectURL(blob);
|
|
84
|
-
downloadLink.download = 'data.' + format;
|
|
85
|
-
|
|
86
|
-
document.body.appendChild(downloadLink);
|
|
87
|
-
downloadLink.click();
|
|
88
|
-
document.body.removeChild(downloadLink);
|
|
89
|
-
}),
|
|
90
|
-
),
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return firstValueFrom(
|
|
95
|
-
this.http
|
|
96
|
-
.get<string>(this.config.apiUrl + this.getQuery(filter), {
|
|
97
|
-
headers: {
|
|
98
|
-
'Content-Type': this._formatMap[format],
|
|
99
|
-
},
|
|
100
|
-
responseType: 'text' as 'json',
|
|
101
|
-
})
|
|
102
|
-
.pipe(
|
|
103
|
-
map((res) => {
|
|
104
|
-
const downloadLink = document.createElement('a');
|
|
105
|
-
const blob = new Blob(['\ufeff', res]);
|
|
106
|
-
|
|
107
|
-
downloadLink.href = URL.createObjectURL(blob);
|
|
108
|
-
downloadLink.download = 'data.' + format;
|
|
109
|
-
|
|
110
|
-
document.body.appendChild(downloadLink);
|
|
111
|
-
downloadLink.click();
|
|
112
|
-
document.body.removeChild(downloadLink);
|
|
113
|
-
}),
|
|
114
|
-
),
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
update(item: T): Promise<void> {
|
|
119
|
-
return firstValueFrom(
|
|
120
|
-
this.http.put<void>(this.config.apiUrl + '/' + item.id, item),
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
updatePartial(item: Partial<T> & { id: string }): Promise<void> {
|
|
125
|
-
return firstValueFrom(
|
|
126
|
-
this.http.patch<void>(this.config.apiUrl + '/' + item.id, item),
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
updatePartialMany(items: (Partial<T> & { id: string })[]): Promise<any> {
|
|
131
|
-
const updatePromises = items.map((item) => {
|
|
132
|
-
return this.updatePartial(item);
|
|
133
|
-
});
|
|
134
|
-
return Promise.all(updatePromises);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
delete(id: string): Promise<void> {
|
|
138
|
-
return firstValueFrom(
|
|
139
|
-
this.http.delete<void>(this.config.apiUrl + '/' + id),
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
protected getQuery(filter: ICrudFilter): string {
|
|
144
|
-
let query = '';
|
|
145
|
-
|
|
146
|
-
if (filter && filter.searchText) {
|
|
147
|
-
query += '&$search=' + filter.searchText;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (filter && filter.limit) {
|
|
151
|
-
query += `&limit=${filter.limit}&offset=${
|
|
152
|
-
filter.offset ? filter.offset : 0
|
|
153
|
-
}`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (filter && filter.sortBy) {
|
|
157
|
-
query += `&sort=${(filter.sortDesc ? '-' : '') + filter.sortBy}`;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (filter && filter.query) {
|
|
161
|
-
filter.query.forEach((q) => {
|
|
162
|
-
if (
|
|
163
|
-
q.value &&
|
|
164
|
-
typeof q.value === 'string' &&
|
|
165
|
-
q.value.match(/^-?\d+$/) &&
|
|
166
|
-
q.value[0] !== "'" &&
|
|
167
|
-
q.value[0] !== '"'
|
|
168
|
-
) {
|
|
169
|
-
query += '&' + q.key + q.type + `'${q.value}'`;
|
|
170
|
-
} else {
|
|
171
|
-
query += '&' + q.key + q.type + q.value;
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return query ? '?' + query.replace('&', '') : '';
|
|
177
|
-
}
|
|
178
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// Mock the facade before any imports
|
|
2
|
-
import { ɵNoopNgZone as NoopNgZone, NgZone } from '@angular/core';
|
|
3
|
-
import { TestBed } from '@angular/core/testing';
|
|
4
|
-
import {
|
|
5
|
-
BrowserTestingModule,
|
|
6
|
-
platformBrowserTesting,
|
|
7
|
-
} from '@angular/platform-browser/testing';
|
|
8
|
-
|
|
9
|
-
import { IEntity } from '@smartsoft001/domain-core';
|
|
10
|
-
|
|
11
|
-
import { CrudListGroupService } from './list-group.service';
|
|
12
|
-
import { CrudFacade } from '../../+state/crud.facade';
|
|
13
|
-
import { ICrudFilter, ICrudListGroup } from '../../models';
|
|
14
|
-
|
|
15
|
-
jest.mock('../../+state/crud.facade', () => ({
|
|
16
|
-
CrudFacade: jest.fn().mockImplementation(() => {
|
|
17
|
-
const filter: ICrudFilter = {
|
|
18
|
-
query: [],
|
|
19
|
-
};
|
|
20
|
-
return {
|
|
21
|
-
filter: jest.fn(() => filter),
|
|
22
|
-
read: jest.fn(() => []),
|
|
23
|
-
};
|
|
24
|
-
}),
|
|
25
|
-
}));
|
|
26
|
-
|
|
27
|
-
export class MockCrudFacade<T> {
|
|
28
|
-
filter: jest.Mock;
|
|
29
|
-
read: jest.Mock;
|
|
30
|
-
|
|
31
|
-
constructor() {
|
|
32
|
-
const filter: ICrudFilter = { query: [] };
|
|
33
|
-
this.filter = jest.fn(() => filter);
|
|
34
|
-
this.read = jest.fn(() => []);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
TestBed.initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
|
|
39
|
-
|
|
40
|
-
describe('crud-shell-angular: CrudListGroupService', () => {
|
|
41
|
-
let service: CrudListGroupService<IEntity<string>>;
|
|
42
|
-
let facade: CrudFacade<IEntity<string>>;
|
|
43
|
-
|
|
44
|
-
beforeEach(() => {
|
|
45
|
-
TestBed.configureTestingModule({
|
|
46
|
-
providers: [
|
|
47
|
-
CrudListGroupService,
|
|
48
|
-
{ provide: CrudFacade, useClass: MockCrudFacade },
|
|
49
|
-
{ provide: NgZone, useClass: NoopNgZone },
|
|
50
|
-
],
|
|
51
|
-
});
|
|
52
|
-
service = TestBed.inject(CrudListGroupService);
|
|
53
|
-
facade = TestBed.inject(CrudFacade);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('should create the service', () => {
|
|
57
|
-
expect(service).toBeTruthy();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('change', () => {
|
|
61
|
-
it('should set item.changed to true if val is truthy', () => {
|
|
62
|
-
const item: ICrudListGroup = { key: 'test', value: 'val' } as any;
|
|
63
|
-
service.change(true, item, false);
|
|
64
|
-
expect(item.changed).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should set item.changed to true if force is true', () => {
|
|
68
|
-
const item: ICrudListGroup = { key: 'test', value: 'val' } as any;
|
|
69
|
-
service.change(false, item, true);
|
|
70
|
-
expect(item.changed).toBe(true);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should add a new query if not found', () => {
|
|
74
|
-
const item: ICrudListGroup = { key: 'test', value: 'val' } as any;
|
|
75
|
-
service.change(true, item, false);
|
|
76
|
-
expect(facade.filter().query.length).toBe(1);
|
|
77
|
-
expect(facade.filter().query[0].key).toBe('test');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should update current.value and set current.hidden to true', () => {
|
|
81
|
-
const item: ICrudListGroup = { key: 'test', value: 'val' } as any;
|
|
82
|
-
service.change(true, item, false);
|
|
83
|
-
expect(facade.filter().query[0].value).toBe('val');
|
|
84
|
-
expect(facade.filter().query[0].hidden).toBe(true);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { inject, Injectable } from '@angular/core';
|
|
2
|
-
import { debounce } from 'lodash-decorators';
|
|
3
|
-
|
|
4
|
-
import { IEntity } from '@smartsoft001/domain-core';
|
|
5
|
-
|
|
6
|
-
import { CrudFacade } from '../../+state/crud.facade';
|
|
7
|
-
import { ICrudListGroup } from '../../models';
|
|
8
|
-
|
|
9
|
-
@Injectable()
|
|
10
|
-
export class CrudListGroupService<T extends IEntity<string>> {
|
|
11
|
-
private facade = inject(CrudFacade<T>);
|
|
12
|
-
|
|
13
|
-
private _destroyed: Array<ICrudListGroup> = [];
|
|
14
|
-
|
|
15
|
-
change(val: boolean, item: ICrudListGroup, force = false): void {
|
|
16
|
-
if (val || force) {
|
|
17
|
-
item.changed = true;
|
|
18
|
-
|
|
19
|
-
let current = this.facade
|
|
20
|
-
.filter()
|
|
21
|
-
.query.find((q) => q.key === item.key && q.type === '=');
|
|
22
|
-
|
|
23
|
-
if (!current) {
|
|
24
|
-
current = {
|
|
25
|
-
key: item.key,
|
|
26
|
-
type: '=',
|
|
27
|
-
value: null,
|
|
28
|
-
};
|
|
29
|
-
this.facade.filter().query.push(current);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
current.value = item.value;
|
|
33
|
-
current.hidden = true;
|
|
34
|
-
|
|
35
|
-
this.facade.read({
|
|
36
|
-
...this.facade.filter(),
|
|
37
|
-
offset: 0,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
destroy(groups: Array<ICrudListGroup>): void {
|
|
43
|
-
this._destroyed = [
|
|
44
|
-
...(this._destroyed ? this._destroyed : []),
|
|
45
|
-
...groups.filter((g) => g.changed),
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
this.refresh();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@debounce(250)
|
|
52
|
-
private refresh(): void {
|
|
53
|
-
const list = this._destroyed;
|
|
54
|
-
this._destroyed = [];
|
|
55
|
-
|
|
56
|
-
const newQuery = this.facade
|
|
57
|
-
.filter()
|
|
58
|
-
.query.filter((q) => !list.some((i) => q.key === i.key));
|
|
59
|
-
|
|
60
|
-
this.facade.read({
|
|
61
|
-
...this.facade.filter(),
|
|
62
|
-
query: newQuery,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|