@yomotech/view-toolkit 0.0.1 → 0.0.3

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.
Files changed (40) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +36 -22
  3. package/src/lib/app-version.ts +2 -0
  4. package/src/lib/containers/card/card.component.css +0 -0
  5. package/src/lib/containers/card/card.component.html +449 -0
  6. package/src/lib/containers/card/card.component.ts +471 -0
  7. package/src/lib/containers/form/form.component.css +0 -0
  8. package/src/lib/containers/form/form.component.html +1 -0
  9. package/src/lib/containers/form/form.component.ts +12 -0
  10. package/src/lib/containers/grid/grid.component.css +0 -0
  11. package/src/lib/containers/grid/grid.component.html +1 -0
  12. package/src/lib/containers/grid/grid.component.ts +12 -0
  13. package/src/lib/containers/pagination/pagination.component.css +0 -0
  14. package/src/lib/containers/pagination/pagination.component.html +1 -0
  15. package/src/lib/containers/pagination/pagination.component.ts +12 -0
  16. package/src/lib/services/app-version-reset.service.ts +59 -0
  17. package/src/lib/services/config.service.ts +128 -0
  18. package/src/lib/services/couchdb.ts +224 -0
  19. package/src/lib/services/mobile-keycloak.service.ts +116 -0
  20. package/src/lib/services/network-log.service.ts +27 -0
  21. package/src/lib/services/notification.service.ts +26 -0
  22. package/src/lib/services/pouchdb.service.ts +183 -0
  23. package/src/lib/services/request.service.ts +125 -0
  24. package/src/lib/services/utility.service.ts +593 -0
  25. package/src/lib/view-toolkit.module.ts +25 -0
  26. package/src/lib/view-toolkit.spec.ts +23 -0
  27. package/src/lib/view-toolkit.ts +15 -0
  28. package/src/public-api.ts +22 -0
  29. package/tsconfig.lib.json +17 -0
  30. package/tsconfig.lib.prod.json +11 -0
  31. package/tsconfig.spec.json +15 -0
  32. package/fesm2022/abdulahad-view-toolkit.mjs +0 -94
  33. package/fesm2022/abdulahad-view-toolkit.mjs.map +0 -1
  34. package/fesm2022/abdulahad061-view-toolkit.mjs +0 -94
  35. package/fesm2022/abdulahad061-view-toolkit.mjs.map +0 -1
  36. package/fesm2022/yomotech-view-toolkit.mjs +0 -95
  37. package/fesm2022/yomotech-view-toolkit.mjs.map +0 -1
  38. package/types/abdulahad-view-toolkit.d.ts +0 -33
  39. package/types/abdulahad061-view-toolkit.d.ts +0 -33
  40. package/types/yomotech-view-toolkit.d.ts +0 -30
@@ -0,0 +1,471 @@
1
+ import { CommonModule } from '@angular/common';
2
+ import { ChangeDetectorRef, Component, EventEmitter, Input, NgZone, Output } from '@angular/core';
3
+ import { FormsModule } from '@angular/forms';
4
+ import { UtilityService } from '../../services/utility.service';
5
+ import { RequestService } from '../../services/request.service';
6
+
7
+ import { HttpClient, HttpHeaders } from '@angular/common/http';
8
+ import { SimpleChanges } from '@angular/core';
9
+ import { OAuthService } from 'angular-oauth2-oidc';
10
+ import { CapacitorHttp } from '@capacitor/core';
11
+
12
+ import { NzIconModule } from 'ng-zorro-antd/icon';
13
+ import { NzButtonModule } from 'ng-zorro-antd/button';
14
+ import { NzDatePickerModule } from 'ng-zorro-antd/date-picker';
15
+ import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal';
16
+
17
+ import { DatePickerModule } from 'primeng/datepicker';
18
+ import { Platform } from '@angular/cdk/platform';
19
+
20
+ @Component({
21
+ selector: 'vt-card',
22
+ imports: [
23
+ CommonModule,
24
+ FormsModule,
25
+ NzIconModule,
26
+ DatePickerModule,
27
+ NzButtonModule,
28
+ NzDatePickerModule,
29
+ NzModalModule,
30
+ // StringComponent,
31
+ // PaginationComponent
32
+ ],
33
+ standalone: true,
34
+ templateUrl: './card.component.html',
35
+ styleUrl: './card.component.css',
36
+ })
37
+ export class CardComponent {
38
+ @Input() itemInfo: any = {};
39
+ @Input() currentPage: number = 0;
40
+ @Input() pageSize: number = 5;
41
+ @Input() selectedLanguageInfo: any = {};
42
+ @Input() pages: number[] = [];
43
+ @Output() cardButtonClick: EventEmitter<any> = new EventEmitter();
44
+ @Output() cardHeaderButtonClick: EventEmitter<any> = new EventEmitter();
45
+ @Output() linkDetails: EventEmitter<any> = new EventEmitter();
46
+ @Output() imageDetails: EventEmitter<any> = new EventEmitter();
47
+ @Output() onPageSizeChange: EventEmitter<any> = new EventEmitter();
48
+ @Output() onPageChange: EventEmitter<any> = new EventEmitter();
49
+ cardPerRow: string = "col-12 md:col-6 lg:col-";
50
+ isLoading: boolean = false;
51
+ layoutSchemaCopy: any = {};
52
+ headerSchemaCopy: any = {};
53
+ footerSchemaCopy: any = {};
54
+
55
+
56
+ constructor(
57
+ private utilityService: UtilityService,
58
+ private platform: Platform,
59
+ private http: HttpClient,
60
+ private cdr: ChangeDetectorRef,
61
+ private zone: NgZone,
62
+ private modalService: NzModalService,
63
+ private oauthService: OAuthService,
64
+ private requestService: RequestService) {
65
+ }
66
+
67
+ ngOnInit() {
68
+ if ((this.platform.ANDROID || this.platform.IOS) && !this.platform?.isBrowser) {
69
+ this.cardPerRow = "col-";
70
+ } else {
71
+ this.cardPerRow = "col-12 md:col-6 lg:col-";
72
+ }
73
+ }
74
+
75
+ ngOnChanges(changes: SimpleChanges): void {
76
+ if (changes['itemInfo']) {
77
+ if (this.itemInfo.layoutSchema) {
78
+ this.layoutSchemaCopy = JSON.parse(JSON.stringify(this.itemInfo.layoutSchema));
79
+ }
80
+ if (this.itemInfo.header) {
81
+ this.headerSchemaCopy = JSON.parse(JSON.stringify(this.itemInfo.header));
82
+ }
83
+ if (this.itemInfo.footer) {
84
+ this.footerSchemaCopy = JSON.parse(JSON.stringify(this.itemInfo.footer));
85
+ }
86
+ }
87
+ if ((changes['selectedLanguageInfo'] || this.selectedLanguageInfo) && this.selectedLanguageInfo.selectedLanguage) {
88
+ this.itemInfo.footer = this.updatebuttonLanguage(this.footerSchemaCopy, this.selectedLanguageInfo.selectedLanguage)
89
+ this.itemInfo.header = this.updatebuttonLanguage(this.headerSchemaCopy, this.selectedLanguageInfo.selectedLanguage)
90
+ this.itemInfo.layoutSchema = this.updateLayoutLanguage(this.layoutSchemaCopy, this.selectedLanguageInfo.selectedLanguage)
91
+ }
92
+ }
93
+
94
+ private safeUpdate(fn: () => void) {
95
+ if (NgZone.isInAngularZone()) {
96
+ fn();
97
+ this.cdr.markForCheck();
98
+ } else {
99
+ this.zone.run(() => {
100
+ fn();
101
+ this.cdr.markForCheck();
102
+ });
103
+ }
104
+ }
105
+
106
+ forceNumericKeyboard(event: any) {
107
+ const input = event?.target as HTMLInputElement;
108
+ if (input && input.tagName === 'INPUT') {
109
+ input.setAttribute('inputmode', 'numeric'); // Android
110
+ input.setAttribute('pattern', '[0-9:]*'); // iOS
111
+ input.setAttribute('type', 'tel'); // strongest signal
112
+ }
113
+ }
114
+
115
+ onDateTyping(event: any) {
116
+ const input = event.target as HTMLInputElement;
117
+ if (!input) return;
118
+ let digits = input.value.replace(/\D/g, '');
119
+ digits = digits.substring(0, 8);
120
+ let formatted = '';
121
+ if (digits.length <= 4) {
122
+ formatted = digits;
123
+ } else if (digits.length <= 6) {
124
+ formatted = `${digits.slice(0, 4)}-${digits.slice(4)}`;
125
+ } else {
126
+ formatted = `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6)}`;
127
+ }
128
+ input.value = formatted;
129
+ }
130
+
131
+ updatebuttonLanguage<T>(
132
+ schema: T,
133
+ translations: Record<string, string>
134
+ ): T {
135
+ const cloned = JSON.parse(JSON.stringify(schema));
136
+
137
+ const walk = (obj: any) => {
138
+ if (!obj || typeof obj !== 'object') return;
139
+
140
+ // ✅ Replace direct caption (e.g. button.caption)
141
+ if (typeof obj.caption === 'string') {
142
+ const original = obj.caption;
143
+ obj.caption = translations[original] ?? original;
144
+ }
145
+
146
+ // ✅ Replace modalTitle if present
147
+ if (typeof obj.modalTitle === 'string') {
148
+ const original = obj.modalTitle;
149
+ obj.modalTitle = translations[original] ?? original;
150
+ }
151
+
152
+ // 🔁 Recurse
153
+ Object.keys(obj).forEach((key) => {
154
+ const value = obj[key];
155
+ if (typeof value === 'object') {
156
+ walk(value);
157
+ }
158
+ });
159
+ };
160
+
161
+ walk(cloned);
162
+ return cloned;
163
+ }
164
+
165
+
166
+ updateLayoutLanguage<T>(
167
+ schema: T,
168
+ translations: Record<string, string>
169
+ ): T {
170
+ // Deep clone to avoid mutating original schema
171
+ const cloned = JSON.parse(JSON.stringify(schema));
172
+
173
+ const walk = (obj: any) => {
174
+ if (!obj || typeof obj !== 'object') return;
175
+
176
+ // ✅ If label.caption exists → translate it
177
+ if (obj.label && typeof obj.label.caption === 'string') {
178
+ const originalCaption = obj.label.caption;
179
+ obj.label.caption =
180
+ translations[originalCaption] ?? originalCaption;
181
+ }
182
+
183
+ // 🔁 Recurse into object properties
184
+ Object.keys(obj).forEach((key) => {
185
+ const value = obj[key];
186
+ if (typeof value === 'object') {
187
+ walk(value);
188
+ }
189
+ });
190
+ };
191
+
192
+ walk(cloned);
193
+ return cloned;
194
+ }
195
+
196
+ dateChangeEvent(filter: any, value: any) {
197
+ const scope = this.getScope(filter.scope.reference);
198
+ const field = filter.scope?.reference.split('/').pop();
199
+ this.itemInfo.propertySchema[scope][field].value = value
200
+ this.applyDateFilter(this.extractFilterValues(this.itemInfo.propertySchema[scope]))
201
+ }
202
+
203
+ async applyDateFilter(filters: any) {
204
+ const from = filters.fromDateFilter ? new Date(filters.fromDateFilter) : null;
205
+ const to = filters.toDateFilter ? new Date(filters.toDateFilter) : null;
206
+ if (to && from) {
207
+ const formattedFrom = this.formatToOffsetISO(from);
208
+ const formattedTo = this.formatToOffsetISO(to);
209
+ const keycloakInfo: any = await this.utilityService.getLocalStorage('KeycloakInfo');
210
+ const logined: any = await this.utilityService.getLocalStorage('loginedUser');
211
+ this.safeUpdate(() => this.isLoading = true);
212
+ if (this.platform.ANDROID || this.platform.IOS) {
213
+ let saveOrder = await this.getSaveOrder(keycloakInfo?.token, keycloakInfo, formattedFrom, formattedTo)
214
+ this.itemInfo.data = saveOrder?.length > 0 ? saveOrder : []
215
+ this.safeUpdate(() => this.isLoading = false);
216
+ } else {
217
+ this.requestService.getSaveOrder(logined?.token, logined, formattedFrom, formattedTo).subscribe((saveOrders: any) => {
218
+ const updatedData = this.utilityService.restoreDatesFromStorage(saveOrders, this.itemInfo.propertySchema);
219
+ this.itemInfo.data = updatedData?.length > 0 ? updatedData : []
220
+ this.safeUpdate(() => this.isLoading = false);
221
+ })
222
+ }
223
+ }
224
+ }
225
+
226
+ async getSaveOrder(
227
+ token: string,
228
+ userInfo: any,
229
+ fromDate: any,
230
+ toDate: any
231
+ ) {
232
+ if (!token) {
233
+ return;
234
+ }
235
+
236
+ const company = userInfo.p5_plant
237
+ ? `${userInfo.p5_group}/${userInfo.p5_client}/${userInfo.p5_plant}`
238
+ : `${userInfo.p5_group}/${userInfo.p5_client}/`;
239
+
240
+ const url = `${userInfo.p5_backend_url}/assetsmobileservice/Zeiterfassung/requestBookedTimeRecording`;
241
+
242
+ try {
243
+ const response = await CapacitorHttp.post({
244
+ url,
245
+ params: {
246
+ resourceNo: userInfo.p5_resource_no,
247
+ workDayFrom: fromDate,
248
+ workDayTo: toDate,
249
+ company,
250
+ appVersion: '1',
251
+ },
252
+ headers: {
253
+ Authorization: `Bearer ${token}`, // ✅ REQUIRED
254
+ Accept: 'application/json',
255
+ },
256
+ // ❗ NO BODY needed for this endpoint
257
+ });
258
+
259
+ console.log('getSaveOrder response:', response.data);
260
+ return response.data;
261
+
262
+ } catch (error) {
263
+ console.error('Failed to fetch saved orders', error);
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ formatToOffsetISO(date: Date): string {
269
+ if (!date) return "";
270
+
271
+ const pad = (n: number, len = 2) => String(n).padStart(len, "0");
272
+ const year = date.getFullYear();
273
+ const month = pad(date.getMonth() + 1);
274
+ const day = pad(date.getDate());
275
+ const hours = pad(date.getHours());
276
+ const minutes = pad(date.getMinutes());
277
+ const seconds = pad(date.getSeconds());
278
+ const ms = pad(date.getMilliseconds(), 4); // 4-digit milliseconds
279
+
280
+ // timezone offset
281
+ const offset = -date.getTimezoneOffset(); // in minutes
282
+ const offSign = offset >= 0 ? "+" : "-";
283
+ const offHours = pad(Math.floor(Math.abs(offset) / 60));
284
+ const offMinutes = pad(Math.abs(offset) % 60);
285
+
286
+ return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}:${ms}${offSign}${offHours}:${offMinutes}`;
287
+ }
288
+
289
+ extractFilterValues(schema: Record<string, any>): Record<string, any> {
290
+ return Object.keys(schema).reduce((acc, key) => {
291
+ if (key.includes('Filter') && schema[key]?.value !== undefined) {
292
+ acc[key] = schema[key].value;
293
+ }
294
+ return acc;
295
+ }, {} as Record<string, any>);
296
+ }
297
+
298
+ invokeOnPageSizeChange(pageSize: any) {
299
+ this.onPageSizeChange.emit(pageSize);
300
+ }
301
+
302
+ invokeGoToPage(page: any) {
303
+ this.onPageChange.emit(page);
304
+ }
305
+
306
+ invokeOnCardHeader(element: any) {
307
+ this.cardHeaderButtonClick.emit(element);
308
+ }
309
+
310
+ invokeOnCardFooter(element: any) {
311
+ // this.cardHeaderButtonClick.emit(element);
312
+ this.d4_SaveAll();
313
+ }
314
+
315
+ async invokeButtonClick(layout: any, record: any, recordIndex: any) {
316
+ if (layout?.option?.action === "delete" && layout?.option?.storageType == "localstorage") {
317
+ if (layout?.option?.deleteMessage) {
318
+ this.showConfirm(layout?.option, recordIndex);
319
+ }
320
+ } else if (layout?.option?.action === "save" && layout?.option?.storageType === "database") {
321
+ const final_array = this.utilityService.convertToDailyLogs(record);
322
+ await this.saveToDb(final_array, recordIndex, layout.option.data_url)
323
+ } else {
324
+ this.cardButtonClick.emit({ layout: layout, record: record, recordIndex: recordIndex });
325
+ }
326
+ }
327
+
328
+ showConfirm(option: any, recordIndex: any) {
329
+ this.modalService.confirm({
330
+ nzTitle: this.selectedLanguageInfo && this.selectedLanguageInfo?.selectedLanguage['Confirm'] ? this.selectedLanguageInfo.selectedLanguage['Confirm'] : 'Confirm',
331
+ nzContent: this.selectedLanguageInfo && this.selectedLanguageInfo?.selectedLanguage[option.deleteMessage] ? this.selectedLanguageInfo.selectedLanguage[option.deleteMessage] : option.deleteMessage,
332
+ nzOkText: this.selectedLanguageInfo && this.selectedLanguageInfo?.selectedLanguage['Delete'] ? this.selectedLanguageInfo.selectedLanguage['Delete'] : 'Delete',
333
+ nzCancelText: this.selectedLanguageInfo && this.selectedLanguageInfo?.selectedLanguage['Cancel'] ? this.selectedLanguageInfo.selectedLanguage['Cancel'] : 'Cancel',
334
+ nzOnOk: () => this.deleteRecord(recordIndex, option.data_url)
335
+ })
336
+ }
337
+
338
+ async d4_SaveAll() {
339
+ let allOrders: any[] = [];
340
+ for (let i = 0; i < this.itemInfo.data.length; i++) {
341
+ const element = this.itemInfo.data[i];
342
+ const dailyLogs = this.utilityService.convertToDailyLogs(element);
343
+ if (Array.isArray(dailyLogs)) {
344
+ allOrders.push(...dailyLogs);
345
+ }
346
+ }
347
+ await this.saveToDb(allOrders, null, true);
348
+ }
349
+
350
+
351
+ deleteRecord(recordIndex: any, data_url: any) {
352
+ this.itemInfo.data.splice(recordIndex, 1);
353
+ this.utilityService.setLocalStorage('timeRecords', JSON.stringify(this.itemInfo.data));
354
+ }
355
+
356
+ deleteAllRecord() {
357
+ this.itemInfo.data = []
358
+ this.utilityService.setLocalStorage('timeRecords', JSON.stringify(this.itemInfo.data));
359
+ }
360
+
361
+ async saveToDb(orders: any, orderIndex?: any, data_url?: any, deleteAll: boolean = false) {
362
+ this.safeUpdate(() => this.isLoading = true);
363
+ const token = this.oauthService.getAccessToken(); // whatever you have
364
+ const logined: any = await this.utilityService.getLocalStorage('loginedUser');
365
+ const keycloakInfo: any = await this.utilityService.getLocalStorage('KeycloakInfo');
366
+ let saveobject = {
367
+ ressourceNo: logined?.p5_resource_no ? logined.p5_resource_no : keycloakInfo?.p5_resource_no,
368
+ returnTimeRecordingLine: orders
369
+ }
370
+ if (this.platform.ANDROID || this.platform.IOS) {
371
+ const res = await this.mobileSaveDb(keycloakInfo.token, keycloakInfo, saveobject, orderIndex, data_url, deleteAll);
372
+ } else {
373
+ this.webSaveDb(token, logined, saveobject, orderIndex, data_url, deleteAll);
374
+ }
375
+ }
376
+
377
+ webSaveDb(token: any, userInfo: any, order: any = {}, orderIndex?: any, dataUrl?: any, deleteAll: boolean = false) {
378
+ if (token) {
379
+ const headers = new HttpHeaders({
380
+ 'Authorization': `Bearer ${token}`,
381
+ 'Accept': 'application/json'
382
+ });
383
+ let url2: any = `${userInfo.p5_backend_url}/assetsmobileservice/Zeiterfassung/registerTimeRecording?company=${userInfo.p5_group}/${userInfo.p5_client}/${userInfo.p5_plant ? userInfo.p5_plant : " "}&appVersion=1&resourceNo=${userInfo.p5_resource_no}`
384
+ this.http.post(url2, order, { headers }).subscribe({
385
+ next: res => {
386
+ this.safeUpdate(() => this.isLoading = false);
387
+ if (orderIndex && orderIndex > -1) {
388
+ this.deleteRecord(orderIndex, dataUrl)
389
+ } else {
390
+ this.deleteAllRecord();
391
+ }
392
+ }
393
+ })
394
+ }
395
+ }
396
+
397
+ async mobileSaveDb(token: string, userInfo: any, order: any = {}, orderIndex: number, dataUrl: any, deleteAll: boolean = false) {
398
+ if (!token) {
399
+ console.log("NO-token", token);
400
+ return;
401
+ }
402
+
403
+ const company = userInfo.p5_plant
404
+ ? `${userInfo.p5_group}/${userInfo.p5_client}/${userInfo.p5_plant}`
405
+ : `${userInfo.p5_group}/${userInfo.p5_client}/`;
406
+
407
+ const url = `${userInfo.p5_backend_url}/assetsmobileservice/Zeiterfassung/registerTimeRecording`;
408
+
409
+ try {
410
+ const response = await CapacitorHttp.post({
411
+ url,
412
+ params: {
413
+ company,
414
+ appVersion: '1',
415
+ resourceNo: userInfo.p5_resource_no,
416
+ },
417
+ headers: {
418
+ Authorization: `Bearer ${token}`,
419
+ Accept: 'application/json',
420
+ 'Content-Type': 'application/json',
421
+ },
422
+ data: order,
423
+ });
424
+ if (orderIndex != null && orderIndex > -1) {
425
+ this.deleteRecord(orderIndex, dataUrl);
426
+ } else {
427
+ this.deleteAllRecord();
428
+ }
429
+ this.safeUpdate(() => this.isLoading = false);
430
+
431
+
432
+ return response.data;
433
+
434
+ } catch (error) {
435
+ this.safeUpdate(() => this.isLoading = false);
436
+ console.error('Failed to save time recording', error);
437
+ throw error;
438
+ }
439
+ }
440
+
441
+ showLinkDetails(option: any, record: any) {
442
+ this.linkDetails.emit({ option: option, record: record })
443
+ }
444
+
445
+ showImageDetails(option: any, record: any) {
446
+ this.imageDetails.emit({ option: option, record: record })
447
+ }
448
+
449
+ getProperty(scope: any) {
450
+ const property = scope ? scope.split('/').pop() : '';
451
+ return { key: property, productSchema: this.itemInfo.propertySchema[this.getScope(scope)][property] };
452
+ }
453
+
454
+ getStyleWithoutClasses(styleObject: any) {
455
+ if (!styleObject) {
456
+ return { classes: '', style: {} };
457
+ }
458
+ const { classes, ...styleWithoutClasses } = styleObject;
459
+ return { classes: classes && classes.length > 0 ? classes[0] : '', style: styleWithoutClasses };
460
+ }
461
+
462
+ getScope(scope: any) {
463
+ let propertyScope = "";
464
+ let start = scope?.indexOf("#/") + 2;
465
+ let end = scope?.indexOf("/", start);
466
+ if (start && end) {
467
+ propertyScope = scope.substring(start, end);
468
+ }
469
+ return propertyScope;
470
+ }
471
+ }
File without changes
@@ -0,0 +1 @@
1
+ <p>form works!</p>
@@ -0,0 +1,12 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'vt-form',
5
+ imports: [],
6
+ standalone: true,
7
+ templateUrl: './form.component.html',
8
+ styleUrl: './form.component.css',
9
+ })
10
+ export class FormComponent {
11
+
12
+ }
File without changes
@@ -0,0 +1 @@
1
+ <p>grid works!</p>
@@ -0,0 +1,12 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'vt-grid',
5
+ imports: [],
6
+ standalone: true,
7
+ templateUrl: './grid.component.html',
8
+ styleUrl: './grid.component.css',
9
+ })
10
+ export class GridComponent {
11
+
12
+ }
@@ -0,0 +1 @@
1
+ <p>pagination works!</p>
@@ -0,0 +1,12 @@
1
+ import { Component } from '@angular/core';
2
+
3
+ @Component({
4
+ selector: 'vt-pagination',
5
+ imports: [],
6
+ standalone: true,
7
+ templateUrl: './pagination.component.html',
8
+ styleUrl: './pagination.component.css',
9
+ })
10
+ export class PaginationComponent {
11
+
12
+ }
@@ -0,0 +1,59 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { Preferences } from '@capacitor/preferences';
3
+ import { APP_VERSION } from '../app-version';
4
+ import { Platform } from '@angular/cdk/platform';
5
+
6
+ const VERSION_KEY = 'APP_VERSION';
7
+
8
+
9
+ @Injectable({
10
+ providedIn: 'root'
11
+ })
12
+ export class AppVersionResetService {
13
+
14
+ constructor(
15
+ private platform: Platform) {
16
+ }
17
+
18
+
19
+ async run(): Promise<void> {
20
+ if (this.platform.ANDROID || this.platform.IOS) {
21
+ await this.clearNativeIfChanged();
22
+ // this.clearWebIfChanged();
23
+ // return;
24
+ }
25
+ }
26
+
27
+ private async clearNativeIfChanged(): Promise<void> {
28
+ const { value: savedVersion } = await Preferences.get({ key: VERSION_KEY });
29
+ if (savedVersion === APP_VERSION) return;
30
+ console.log(`🧹 Clearing Capacitor Preferences (new build ${APP_VERSION})`);
31
+ await Preferences.clear();
32
+ this.clearWebStores();
33
+ await Preferences.set({ key: VERSION_KEY, value: APP_VERSION });
34
+ }
35
+
36
+ // private clearWebIfChanged(): void {
37
+ // const savedVersion = sessionStorage.getItem(VERSION_KEY) || localStorage.getItem(VERSION_KEY);
38
+ // if (savedVersion === APP_VERSION) return;
39
+ // this.clearWebStores();
40
+ // localStorage.setItem(VERSION_KEY, APP_VERSION);
41
+ // sessionStorage.setItem(VERSION_KEY, APP_VERSION);
42
+ // }
43
+
44
+ private clearWebStores(): void {
45
+ try { localStorage.clear(); } catch { }
46
+ try { sessionStorage.clear(); } catch { }
47
+ try {
48
+ if (typeof document !== 'undefined' && document.cookie) {
49
+ document.cookie.split(';').forEach((c) => {
50
+ const eqPos = c.indexOf('=');
51
+ const name = eqPos > -1 ? c.substring(0, eqPos).trim() : c.trim();
52
+ if (name) {
53
+ document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/`;
54
+ }
55
+ });
56
+ }
57
+ } catch { }
58
+ }
59
+ }