@codex-ts/core-lib 1.0.9 → 1.0.13

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 (30) hide show
  1. package/fesm2022/codex-ts-core-lib.mjs +2388 -1794
  2. package/fesm2022/codex-ts-core-lib.mjs.map +1 -1
  3. package/lib/components/base-form.component.d.ts +27 -22
  4. package/lib/components/base-list-page.component.d.ts +12 -71
  5. package/lib/components/base-list-tabbed-form.component.d.ts +40 -0
  6. package/lib/components/base-tabbed-form.component.d.ts +17 -20
  7. package/lib/components/base-table.component.d.ts +72 -0
  8. package/lib/components/base-task-details.component.d.ts +59 -0
  9. package/lib/enums/task-role.enum.d.ts +37 -0
  10. package/lib/keycloak/keycloak-config.interface.d.ts +19 -0
  11. package/lib/keycloak/keycloak.guard.d.ts +25 -0
  12. package/lib/keycloak/keycloak.service.d.ts +24 -0
  13. package/lib/keycloak/keycloak.token.d.ts +3 -0
  14. package/lib/models/base-create-request.model.d.ts +12 -0
  15. package/lib/models/base-request.model.d.ts +9 -0
  16. package/lib/models/base-response.model.d.ts +15 -0
  17. package/lib/models/base-task.model.d.ts +17 -0
  18. package/lib/models/base-update-request.model.d.ts +16 -0
  19. package/lib/models/base.model.d.ts +59 -0
  20. package/lib/models/pagination/page.interface.d.ts +23 -6
  21. package/lib/services/base-task.service.d.ts +40 -0
  22. package/lib/services/form.service.d.ts +31 -0
  23. package/lib/states/form-component.state.d.ts +12 -0
  24. package/lib/states/form.state.d.ts +21 -0
  25. package/lib/states/list-tabbed-form.state.d.ts +9 -0
  26. package/lib/states/tabbed-form.state.d.ts +21 -0
  27. package/package.json +7 -2
  28. package/public-api.d.ts +29 -21
  29. package/lib/services/entity-registry.service.d.ts +0 -26
  30. package/lib/services/reference-data-provider.service.d.ts +0 -8
@@ -1,17 +1,17 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Directive, Input, Component, ViewChild, Pipe, NgModule } from '@angular/core';
3
- import { BehaviorSubject, finalize, Observable, forkJoin, throwError, map, take, of, isObservable } from 'rxjs';
4
- import * as i1 from 'primeng/api';
5
- import * as i1$1 from '@angular/common/http';
2
+ import { InjectionToken, Inject, Injectable, Component, NgModule, Directive, Input, ViewChild, Pipe } from '@angular/core';
3
+ import { map, retry, timeout, catchError, take, finalize as finalize$1, switchMap, takeUntil, mergeMap, tap, debounceTime, distinctUntilChanged } from 'rxjs/operators';
4
+ import { BehaviorSubject, finalize, Observable, forkJoin, throwError, of, isObservable, Subject, take as take$1, tap as tap$1, map as map$1, catchError as catchError$1, takeUntil as takeUntil$1, ReplaySubject, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
5
+ import * as i1 from '@angular/common/http';
6
6
  import { HttpParams } from '@angular/common/http';
7
- import { retry, timeout, catchError, finalize as finalize$1 } from 'rxjs/operators';
8
- import * as i1$4 from '@angular/forms';
7
+ import * as i1$1 from '@angular/router';
8
+ import * as i2 from 'primeng/api';
9
+ import * as i1$2 from '@angular/forms';
9
10
  import { FormGroup, ReactiveFormsModule } from '@angular/forms';
10
- import * as i1$2 from '@angular/router';
11
+ import * as i2$1 from '@ngx-formly/core';
12
+ import { FieldType, FormlyModule, FieldWrapper } from '@ngx-formly/core';
11
13
  import * as i1$3 from '@angular/common';
12
14
  import { CommonModule, formatDate } from '@angular/common';
13
- import * as i2 from '@ngx-formly/core';
14
- import { FieldType, FormlyModule, FieldWrapper } from '@ngx-formly/core';
15
15
  import * as i3 from 'primeng/inputtext';
16
16
  import { InputTextModule } from 'primeng/inputtext';
17
17
  import * as i4 from 'primeng/select';
@@ -28,300 +28,269 @@ import * as i3$3 from 'primeng/checkbox';
28
28
  import { CheckboxModule } from 'primeng/checkbox';
29
29
  import * as i3$4 from 'primeng/inputtextarea';
30
30
  import { Textarea } from 'primeng/inputtextarea';
31
- import * as i2$1 from 'primeng/tooltip';
31
+ import * as i2$2 from 'primeng/tooltip';
32
32
  import { TooltipModule } from 'primeng/tooltip';
33
33
 
34
+ class AppConstants {
35
+ static FORM = {
36
+ MODE: 'mode',
37
+ UUID: 'uuid',
38
+ NEW: 'new'
39
+ };
40
+ static PAGE_MODE = {
41
+ CREATE: 'create',
42
+ EDIT: 'edit',
43
+ VIEW: 'view'
44
+ };
45
+ }
46
+
47
+ const AppMessages = {
48
+ COMMON: {
49
+ ERROR: {
50
+ GENERAL: 'An error occurred',
51
+ INVALID_INPUT: 'Invalid input provided',
52
+ REQUIRED_FIELD: 'This field is required'
53
+ },
54
+ SUCCESS: {
55
+ SAVE: 'saved successfully',
56
+ UPDATE: 'updated successfully',
57
+ DELETE: 'deleted successfully'
58
+ }
59
+ },
60
+ FORM: {
61
+ ERROR: {
62
+ INIT: 'Failed to initialize form',
63
+ SAVE: 'Failed to save',
64
+ LOAD: 'Failed to load',
65
+ VALIDATION: 'Please fill in all required fields correctly'
66
+ },
67
+ SUCCESS: {
68
+ SAVE: 'saved successfully'
69
+ }
70
+ },
71
+ LIST: {
72
+ ERROR: {
73
+ LOAD: 'Failed to load',
74
+ DELETE: 'Failed to delete',
75
+ NAVIGATION: 'Navigation failed',
76
+ INVALID_ID: 'Cannot perform action without ID'
77
+ },
78
+ SUCCESS: {
79
+ DELETE: 'deleted successfully'
80
+ }
81
+ }
82
+ };
83
+
84
+ var ComponentContext;
85
+ (function (ComponentContext) {
86
+ ComponentContext["STANDALONE"] = "STANDALONE";
87
+ ComponentContext["TABBED"] = "TABBED";
88
+ ComponentContext["MODAL"] = "MODAL";
89
+ ComponentContext["EMBEDDED"] = "EMBEDDED";
90
+ })(ComponentContext || (ComponentContext = {}));
91
+
92
+ var TabbedFormType;
93
+ (function (TabbedFormType) {
94
+ TabbedFormType["STANDARD"] = "STANDARD";
95
+ TabbedFormType["LIST"] = "LIST";
96
+ })(TabbedFormType || (TabbedFormType = {}));
97
+
34
98
  /**
35
- * A utility service that provides common reusable functionality
36
- * such as UUID validation and error message display
99
+ * Standard task roles that define user permissions within a task workflow.
100
+ * These roles control what operations a user can perform on tasks.
37
101
  */
38
- class UtilsService {
39
- /**
40
- * Validates if a string is a valid UUID
41
- * @param uuid The string to validate
42
- * @returns boolean indicating if the string is a valid UUID
43
- */
44
- isValidUUID(uuid) {
45
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
46
- return uuidRegex.test(uuid);
47
- }
102
+ var TaskRole;
103
+ (function (TaskRole) {
48
104
  /**
49
- * Shows an error message using PrimeNG's MessageService
50
- * @param message The error message to display
51
- * @param messageService The MessageService instance to use
105
+ * Can create new tasks. This could be a user initiating a task manually
106
+ * or a system process creating tasks automatically.
52
107
  */
53
- showError(message, messageService) {
54
- if (messageService) {
55
- messageService.add({
56
- severity: 'error',
57
- summary: 'Error',
58
- detail: message,
59
- life: 3000
60
- });
61
- }
62
- }
108
+ TaskRole["TASK_CREATOR"] = "TASK_CREATOR";
63
109
  /**
64
- * Shows a success message using PrimeNG's MessageService
65
- * @param message The success message to display
66
- * @param messageService The MessageService instance to use
110
+ * Can assign or reassign tasks to other users. Typically used by
111
+ * team leads or workflow managers to distribute work.
67
112
  */
68
- showSuccess(message, messageService) {
69
- if (messageService) {
70
- messageService.add({
71
- severity: 'success',
72
- summary: 'Success',
73
- detail: message,
74
- life: 3000
75
- });
76
- }
77
- }
113
+ TaskRole["TASK_ASSIGNER"] = "TASK_ASSIGNER";
78
114
  /**
79
- * Gets the display value for an enum key or array of enum keys
80
- * @param enumType The enum type
81
- * @param key The enum key or array of enum keys
82
- * @returns The display value for the enum key or comma-separated list of display values for array of keys
115
+ * Can pick tasks for self-assignment from a task pool.
116
+ * Useful in scenarios where tasks can be claimed by available workers.
83
117
  */
84
- getEnumValue(enumType, key) {
85
- if (!key)
86
- return '';
87
- if (Array.isArray(key)) {
88
- // Handle array of keys - map each to its display value and join with comma
89
- return key.map(k => enumType[k] || k).join(', ');
90
- }
91
- // Handle single key
92
- return enumType[key] || key;
93
- }
118
+ TaskRole["TASK_PICKER"] = "TASK_PICKER";
94
119
  /**
95
- * Gets all values from an enum type for use in formly select fields
96
- * @param enumType The enum type to get values from
97
- * @returns Array of {label, value} pairs for formly select options
120
+ * Can review and approve completed tasks. This role is responsible for
121
+ * quality assurance or approval steps in the workflow.
98
122
  */
99
- getEnumValues(enumType) {
100
- return Object.entries(enumType)
101
- .filter(([key]) => isNaN(Number(key))) // Filter out reverse mappings
102
- .map(([key, value]) => ({
103
- value: key, // enum key as the value
104
- label: value // enum value as the label
105
- }));
106
- }
123
+ TaskRole["TASK_REVIEWER"] = "TASK_REVIEWER";
107
124
  /**
108
- * Converts a camelCase string to Title Case with spaces
109
- * @param camelCase The camelCase string to convert
110
- * @returns The Title Case string
125
+ * Can view task details but cannot modify them. Useful for stakeholders
126
+ * who need to monitor task progress without making changes.
111
127
  */
112
- camelCaseToTitleCase(camelCase) {
113
- if (!camelCase)
114
- return '';
115
- const spacedString = camelCase.replace(/([a-z])([A-Z])/g, '$1 $2');
116
- return spacedString.charAt(0).toUpperCase() + spacedString.slice(1);
117
- }
128
+ TaskRole["TASK_VIEWER"] = "TASK_VIEWER";
118
129
  /**
119
- * Converts a string to kebab-case
120
- * @param input The string to convert
121
- * @returns The kebab-case string
130
+ * Has full administrative control over tasks. Can override or force
131
+ * state changes, bypass normal workflow restrictions, and manage
132
+ * task configuration.
122
133
  */
123
- toKebabCase(input) {
124
- return input
125
- .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
126
- .replace(/([A-Z])([A-Z])(?=[a-z])/g, '$1-$2')
127
- .toLowerCase();
134
+ TaskRole["TASK_ADMIN"] = "TASK_ADMIN";
135
+ })(TaskRole || (TaskRole = {}));
136
+
137
+ const KEYCLOAK_CONFIG = new InjectionToken('KEYCLOAK_CONFIG');
138
+
139
+ class KeycloakService {
140
+ config;
141
+ http;
142
+ keycloak;
143
+ isAuthenticated$ = new BehaviorSubject(false);
144
+ userProfile$ = new BehaviorSubject(null);
145
+ constructor(config, http) {
146
+ this.config = config;
147
+ this.http = http;
128
148
  }
129
- /**
130
- * Converts a string to camelCase.
131
- * Example: "loan-application" becomes "loanApplication"
132
- * @param input The string to convert
133
- * @returns The camelCase string
134
- */
135
- toCamelCase(input) {
136
- if (!input)
137
- return '';
138
- // Split by non-alphanumeric chars
139
- const words = input.split(/[^a-zA-Z0-9]/);
140
- const result = [words[0].toLowerCase()];
141
- // Capitalize first letter of remaining words
142
- for (let i = 1; i < words.length; i++) {
143
- const word = words[i];
144
- if (word) {
145
- result.push(word.charAt(0).toUpperCase() + word.substring(1).toLowerCase());
149
+ async initialize() {
150
+ try {
151
+ this.keycloak = new Keycloak(this.config);
152
+ const authenticated = await this.keycloak.init({
153
+ onLoad: 'check-sso',
154
+ silentCheckSsoRedirectUri: this.config.silentCheckSsoRedirectUri
155
+ });
156
+ this.isAuthenticated$.next(authenticated);
157
+ if (authenticated) {
158
+ await this.loadUserProfile();
146
159
  }
160
+ return authenticated;
161
+ }
162
+ catch (error) {
163
+ console.error('Failed to initialize Keycloak:', error);
164
+ return false;
147
165
  }
148
- return result.join('');
149
166
  }
150
- /**
151
- * Removes double forward slashes from an endpoint string while preserving the protocol
152
- * e.g. "http://localhost:8080//api//v1//users" -> "http://localhost:8080/api/v1/users"
153
- * @param endpoint The endpoint string to clean
154
- * @returns The cleaned endpoint string without double slashes
155
- */
156
- removeDoubleSlashes(endpoint) {
157
- if (!endpoint)
158
- return '';
159
- // Split the URL into protocol and the rest to preserve protocol slashes
160
- const [protocol, ...rest] = endpoint.split('://');
161
- if (!rest.length) {
162
- // If no protocol is present, just clean the entire string
163
- return endpoint.replace(/\/+/g, '/');
167
+ login(options = {}) {
168
+ const loginOptions = {
169
+ ...this.config.loginOptions,
170
+ ...options
171
+ };
172
+ return this.keycloak.login(loginOptions);
173
+ }
174
+ logout() {
175
+ return this.keycloak.logout();
176
+ }
177
+ isLoggedIn() {
178
+ return this.isAuthenticated$.asObservable();
179
+ }
180
+ async getToken() {
181
+ try {
182
+ await this.updateToken(this.config.tokenValidityInSeconds || 60);
183
+ return this.keycloak.token;
184
+ }
185
+ catch (error) {
186
+ console.error('Failed to get token:', error);
187
+ return null;
164
188
  }
165
- // Clean the path part (after protocol) and reconstruct the URL
166
- const cleanPath = rest.join('://').replace(/\/+/g, '/');
167
- return `${protocol}://${cleanPath}`;
168
189
  }
169
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
170
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UtilsService, providedIn: 'root' });
190
+ getRoles() {
191
+ if (!this.keycloak?.realmAccess?.roles) {
192
+ return [];
193
+ }
194
+ return this.keycloak.realmAccess.roles;
195
+ }
196
+ hasRole(role) {
197
+ return this.getRoles().includes(role);
198
+ }
199
+ getUserProfile() {
200
+ return this.userProfile$.asObservable();
201
+ }
202
+ updateToken(minValidity = 60) {
203
+ return new Promise((resolve, reject) => {
204
+ if (!this.keycloak?.token) {
205
+ resolve(false);
206
+ return;
207
+ }
208
+ this.keycloak.updateToken(minValidity)
209
+ .then((refreshed) => {
210
+ resolve(refreshed);
211
+ })
212
+ .catch((error) => {
213
+ console.error('Failed to update token:', error);
214
+ reject(error);
215
+ });
216
+ });
217
+ }
218
+ async loadUserProfile() {
219
+ try {
220
+ const profile = await this.keycloak.loadUserProfile();
221
+ this.userProfile$.next(profile);
222
+ }
223
+ catch (error) {
224
+ console.error('Failed to load user profile:', error);
225
+ }
226
+ }
227
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakService, deps: [{ token: KEYCLOAK_CONFIG }, { token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
228
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakService, providedIn: 'root' });
171
229
  }
172
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UtilsService, decorators: [{
230
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakService, decorators: [{
173
231
  type: Injectable,
174
- args: [{
175
- providedIn: 'root'
176
- }]
177
- }] });
232
+ args: [{ providedIn: 'root' }]
233
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
234
+ type: Inject,
235
+ args: [KEYCLOAK_CONFIG]
236
+ }] }, { type: i1.HttpClient }] });
178
237
 
179
- class EnumRegistryService {
180
- utilsService;
181
- enumMap = new Map();
182
- constructor(utilsService) {
183
- this.utilsService = utilsService;
238
+ class KeycloakAuthGuard {
239
+ keycloakService;
240
+ router;
241
+ constructor(keycloakService, router) {
242
+ this.keycloakService = keycloakService;
243
+ this.router = router;
184
244
  }
185
- /**
186
- * Initialize global enums
187
- */
188
- static registerGlobalEnums(enums) {
189
- return {
190
- provide: EnumRegistryService,
191
- useFactory: (utilsService) => {
192
- const service = new EnumRegistryService(utilsService);
193
- Object.entries(enums).forEach(([name, enumType]) => {
194
- service.register(name, enumType);
245
+ canActivate(route, state) {
246
+ return this.keycloakService.isLoggedIn().pipe(map(authenticated => {
247
+ // Handle authentication check
248
+ if (!authenticated) {
249
+ this.keycloakService.login({
250
+ redirectUri: window.location.origin + state.url
195
251
  });
196
- return service;
197
- },
198
- deps: [UtilsService]
199
- };
252
+ return false;
253
+ }
254
+ // Handle role-based access
255
+ const requiredRoles = route.data['roles'];
256
+ if (requiredRoles && !this.hasRequiredRoles(requiredRoles)) {
257
+ // Redirect to unauthorized page if roles don't match
258
+ return this.router.createUrlTree(['/unauthorized']);
259
+ }
260
+ return true;
261
+ }));
262
+ }
263
+ hasRequiredRoles(requiredRoles) {
264
+ const userRoles = this.keycloakService.getRoles();
265
+ return requiredRoles.every(role => userRoles.includes(role));
200
266
  }
201
267
  /**
202
- * Register an enum type with the service
203
- * @param name Name to register the enum under
204
- * @param enumType The enum type to register
268
+ * Checks if all required roles are present
269
+ * @param route Route being activated
270
+ * @returns true if no roles are required or if all required roles are present
205
271
  */
206
- register(name, enumType) {
207
- this.enumMap.set(name, enumType);
272
+ checkRoles(route) {
273
+ const requiredRoles = route.data['roles'];
274
+ if (!requiredRoles || requiredRoles.length === 0) {
275
+ return true;
276
+ }
277
+ return this.hasRequiredRoles(requiredRoles);
208
278
  }
209
279
  /**
210
- * Get formly-compatible options array for a registered enum
211
- * @param name Name of the registered enum
212
- * @returns Array of {label, value} pairs suitable for formly select fields
280
+ * Gets redirect URL from route if present
281
+ * @param state Router state
282
+ * @returns URL to redirect to after login or undefined
213
283
  */
214
- getEnumValues(name) {
215
- const enumType = this.enumMap.get(name);
216
- if (!enumType) {
217
- console.warn(`Enum type "${name}" not found in registry`);
218
- return [];
219
- }
220
- return this.utilsService.getEnumValues(enumType);
221
- }
222
- /**
223
- * Check if an enum type is registered
224
- * @param name Name of the enum to check
225
- */
226
- hasEnum(name) {
227
- return this.enumMap.has(name);
228
- }
229
- /**
230
- * Clear all registered enums
231
- */
232
- clear() {
233
- this.enumMap.clear();
234
- }
235
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EnumRegistryService, deps: [{ token: UtilsService }], target: i0.ɵɵFactoryTarget.Injectable });
236
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EnumRegistryService, providedIn: 'root' });
237
- }
238
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EnumRegistryService, decorators: [{
239
- type: Injectable,
240
- args: [{
241
- providedIn: 'root'
242
- }]
243
- }], ctorParameters: () => [{ type: UtilsService }] });
244
-
245
- class FormlyConfigService {
246
- enumRegistry;
247
- constructor(enumRegistry) {
248
- this.enumRegistry = enumRegistry;
249
- }
250
- getFormConfig(fields, mode, dynamicOptions) {
251
- const commonConfig = {
252
- props: {
253
- disabled: mode === 'view'
254
- }
255
- };
256
- return fields.map((field) => {
257
- // Apply dynamic options from child component
258
- if (dynamicOptions && field.key && typeof field.key === 'string') {
259
- const fieldOptions = dynamicOptions[field.key];
260
- if (fieldOptions) {
261
- field.props = {
262
- ...field.props,
263
- ...fieldOptions
264
- };
265
- }
266
- }
267
- // Handle field groups recursively
268
- if (field.fieldGroup) {
269
- field.fieldGroup = this.getFormConfig(field.fieldGroup, mode, dynamicOptions);
270
- }
271
- return {
272
- ...field,
273
- ...commonConfig,
274
- props: {
275
- ...field.props,
276
- ...commonConfig.props
277
- }
278
- };
279
- });
284
+ getLoginRedirectUrl(state) {
285
+ return window.location.origin + state.url;
280
286
  }
281
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormlyConfigService, deps: [{ token: EnumRegistryService }], target: i0.ɵɵFactoryTarget.Injectable });
282
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormlyConfigService, providedIn: 'root' });
287
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, deps: [{ token: KeycloakService }, { token: i1$1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
288
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, providedIn: 'root' });
283
289
  }
284
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormlyConfigService, decorators: [{
290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: KeycloakAuthGuard, decorators: [{
285
291
  type: Injectable,
286
- args: [{
287
- providedIn: 'root'
288
- }]
289
- }], ctorParameters: () => [{ type: EnumRegistryService }] });
290
-
291
- class NotificationService {
292
- messageService;
293
- messageSubject = new BehaviorSubject(null);
294
- message$ = this.messageSubject.asObservable();
295
- constructor(messageService) {
296
- this.messageService = messageService;
297
- }
298
- showSuccess(message) {
299
- const msgObj = {
300
- severity: 'success',
301
- summary: 'Success',
302
- detail: message,
303
- life: 3000
304
- };
305
- this.messageSubject.next(msgObj);
306
- }
307
- showError(message) {
308
- const msgObj = {
309
- severity: 'error',
310
- summary: 'Error',
311
- detail: message,
312
- life: 3000
313
- };
314
- this.messageSubject.next(msgObj);
315
- }
316
- clearMessage() {
317
- this.messageSubject.next(null);
318
- }
319
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NotificationService, deps: [{ token: i1.MessageService }], target: i0.ɵɵFactoryTarget.Injectable });
320
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NotificationService });
321
- }
322
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NotificationService, decorators: [{
323
- type: Injectable
324
- }], ctorParameters: () => [{ type: i1.MessageService }] });
292
+ args: [{ providedIn: 'root' }]
293
+ }], ctorParameters: () => [{ type: KeycloakService }, { type: i1$1.Router }] });
325
294
 
326
295
  /**
327
296
  * A general-purpose HTTP utility service that provides enhanced HTTP operations
@@ -601,7 +570,7 @@ class HttpUtilityService {
601
570
  });
602
571
  }
603
572
  }
604
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: HttpUtilityService, deps: [{ token: i1$1.HttpClient }, { token: i1.MessageService }], target: i0.ɵɵFactoryTarget.Injectable });
573
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: HttpUtilityService, deps: [{ token: i1.HttpClient }, { token: i2.MessageService }], target: i0.ɵɵFactoryTarget.Injectable });
605
574
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: HttpUtilityService, providedIn: 'root' });
606
575
  }
607
576
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: HttpUtilityService, decorators: [{
@@ -609,547 +578,1487 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImpor
609
578
  args: [{
610
579
  providedIn: 'root'
611
580
  }]
612
- }], ctorParameters: () => [{ type: i1$1.HttpClient }, { type: i1.MessageService }] });
613
-
614
- class ReferenceDataProviderService {
615
- endpoints = {};
616
- // Method to set/update endpoints
617
- setEndpoints(endpoints) {
618
- this.endpoints = { ...this.endpoints, ...endpoints };
619
- }
620
- getReferenceEndpoint(entityName) {
621
- console.log('getReferenceEndpoint', entityName, this.endpoints);
622
- let endpoint = this.endpoints[entityName];
623
- if (endpoint != null && endpoint.length > 0) {
624
- if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {
625
- return endpoint; // Absolute URL
626
- }
627
- else {
628
- // Relative URL, assume it's within the application's base context
629
- return endpoint;
630
- }
631
- }
632
- return null;
633
- }
634
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ReferenceDataProviderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
635
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ReferenceDataProviderService, providedIn: 'root' });
636
- }
637
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: ReferenceDataProviderService, decorators: [{
638
- type: Injectable,
639
- args: [{
640
- providedIn: 'root'
641
- }]
642
- }] });
581
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.MessageService }] });
643
582
 
644
- class EntityRegistryService {
645
- httpUtility;
646
- dataProvider;
647
- constructor(httpUtility, dataProvider) {
648
- this.httpUtility = httpUtility;
649
- this.dataProvider = dataProvider;
583
+ /**
584
+ * A utility service that provides common reusable functionality
585
+ * such as UUID validation and error message display
586
+ */
587
+ class UtilsService {
588
+ /**
589
+ * Validates if a string is a valid UUID
590
+ * @param uuid The string to validate
591
+ * @returns boolean indicating if the string is a valid UUID
592
+ */
593
+ isValidUUID(uuid) {
594
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
595
+ return uuidRegex.test(uuid);
650
596
  }
651
597
  /**
652
- * Gets reference options for an entity, suitable for select/dropdown components
653
- * @param entityName The name of the entity (e.g., 'customers', 'departments')
654
- * @returns Observable of options array with label/value pairs
598
+ * Shows an error message using PrimeNG's MessageService
599
+ * @param message The error message to display
600
+ * @param messageService The MessageService instance to use
655
601
  */
656
- getEntityOptions(entityName) {
657
- console.log('getEntityOptions', entityName);
658
- const url = this.dataProvider.getReferenceEndpoint(entityName);
659
- if (!url) {
660
- console.warn(`No endpoint configured for entity: ${entityName}`);
661
- return new Observable(subscriber => {
662
- subscriber.next([]);
663
- subscriber.complete();
602
+ showError(message, messageService) {
603
+ if (messageService) {
604
+ messageService.add({
605
+ severity: 'error',
606
+ summary: 'Error',
607
+ detail: message,
608
+ life: 3000
664
609
  });
665
610
  }
666
- return this.httpUtility.get(url, {
667
- retries: 1,
668
- timeoutMs: 10000,
669
- }).pipe(map((items) => items?.map((item) => ({
670
- label: item.displayLabel,
671
- value: item.key,
672
- data: item.data,
673
- })) || []));
674
- }
675
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EntityRegistryService, deps: [{ token: HttpUtilityService }, { token: ReferenceDataProviderService }], target: i0.ɵɵFactoryTarget.Injectable });
676
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EntityRegistryService, providedIn: 'root' });
677
- }
678
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EntityRegistryService, decorators: [{
679
- type: Injectable,
680
- args: [{
681
- providedIn: 'root'
682
- }]
683
- }], ctorParameters: () => [{ type: HttpUtilityService }, { type: ReferenceDataProviderService }] });
684
-
685
- class BaseCrudService {
686
- httpUtility;
687
- utilsService;
688
- resourcePath;
689
- backendBaseUrl;
690
- messageService;
691
- // Use the loading state from HttpUtilityService
692
- get loading$() {
693
- return this.httpUtility.loading$;
694
611
  }
695
- apiUrl;
696
612
  /**
697
- * Creates an instance of BaseCrudService.
698
- * @param httpUtility - Service for making HTTP requests
699
- * @param utilsService - Utility service for common operations
700
- * @param resourcePath - API resource path (e.g., 'customers', 'products')
701
- * @param backendBaseUrl - Base URL for the backend API
702
- * @param messageService - Optional service for showing messages
613
+ * Shows a success message using PrimeNG's MessageService
614
+ * @param message The success message to display
615
+ * @param messageService The MessageService instance to use
703
616
  */
704
- constructor(httpUtility, utilsService, resourcePath, backendBaseUrl, messageService) {
705
- this.httpUtility = httpUtility;
706
- this.utilsService = utilsService;
707
- this.resourcePath = resourcePath;
708
- this.backendBaseUrl = backendBaseUrl;
709
- this.messageService = messageService;
710
- this.apiUrl = this.joinUrls(backendBaseUrl, resourcePath);
617
+ showSuccess(message, messageService) {
618
+ if (messageService) {
619
+ messageService.add({
620
+ severity: 'success',
621
+ summary: 'Success',
622
+ detail: message,
623
+ life: 3000
624
+ });
625
+ }
711
626
  }
712
627
  /**
713
- * Joins URL parts ensuring no double slashes
628
+ * Gets the display value for an enum key or array of enum keys
629
+ * @param enumType The enum type
630
+ * @param key The enum key or array of enum keys
631
+ * @returns The display value for the enum key or comma-separated list of display values for array of keys
714
632
  */
715
- joinUrls(...parts) {
716
- return parts.map(part => part.replace(/^\/+|\/+$/g, '')).join('/');
717
- }
718
- getAll(pageRequest) {
719
- if (pageRequest) {
720
- return this.httpUtility.post(`${this.apiUrl}/page`, pageRequest);
721
- }
722
- else {
723
- return this.httpUtility.get(this.apiUrl);
633
+ getEnumValue(enumType, key) {
634
+ if (!key)
635
+ return '';
636
+ if (Array.isArray(key)) {
637
+ // Handle array of keys - map each to its display value and join with comma
638
+ return key.map(k => enumType[k] || k).join(', ');
724
639
  }
640
+ // Handle single key
641
+ return enumType[key] || key;
725
642
  }
726
- getById(uuid) {
727
- if (!this.utilsService.isValidUUID(uuid)) {
728
- this.utilsService.showError('Invalid UUID format', this.messageService);
729
- return throwError(() => new Error('Invalid UUID format'));
730
- }
731
- return this.httpUtility.get(`${this.apiUrl}/${uuid}`);
643
+ /**
644
+ * Gets all values from an enum type for use in formly select fields
645
+ * @param enumType The enum type to get values from
646
+ * @returns Array of {label, value} pairs for formly select options
647
+ */
648
+ getEnumValues(enumType) {
649
+ return Object.entries(enumType)
650
+ .filter(([key]) => isNaN(Number(key))) // Filter out reverse mappings
651
+ .map(([key, value]) => ({
652
+ value: key, // enum key as the value
653
+ label: value // enum value as the label
654
+ }));
732
655
  }
733
- create(entity) {
734
- return this.httpUtility.post(this.apiUrl, entity);
656
+ /**
657
+ * Converts a camelCase string to Title Case with spaces
658
+ * @param camelCase The camelCase string to convert
659
+ * @returns The Title Case string
660
+ */
661
+ camelCaseToTitleCase(camelCase) {
662
+ if (!camelCase)
663
+ return '';
664
+ const spacedString = camelCase.replace(/([a-z])([A-Z])/g, '$1 $2');
665
+ return spacedString.charAt(0).toUpperCase() + spacedString.slice(1);
735
666
  }
736
- update(uuid, entity) {
737
- if (!this.utilsService.isValidUUID(uuid)) {
738
- this.utilsService.showError('Invalid UUID format', this.messageService);
739
- return throwError(() => new Error('Invalid UUID format'));
740
- }
741
- return this.httpUtility.put(`${this.apiUrl}/${uuid}`, entity);
667
+ /**
668
+ * Converts a string to kebab-case
669
+ * @param input The string to convert
670
+ * @returns The kebab-case string
671
+ */
672
+ toKebabCase(input) {
673
+ return input
674
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
675
+ .replace(/([A-Z])([A-Z])(?=[a-z])/g, '$1-$2')
676
+ .toLowerCase();
742
677
  }
743
- delete(uuid) {
744
- if (!this.utilsService.isValidUUID(uuid)) {
745
- this.utilsService.showError('Invalid UUID format', this.messageService);
746
- return throwError(() => new Error('Invalid UUID format'));
678
+ /**
679
+ * Converts a string to camelCase.
680
+ * Example: "loan-application" becomes "loanApplication"
681
+ * @param input The string to convert
682
+ * @returns The camelCase string
683
+ */
684
+ toCamelCase(input) {
685
+ if (!input)
686
+ return '';
687
+ // Split by non-alphanumeric chars
688
+ const words = input.split(/[^a-zA-Z0-9]/);
689
+ const result = [words[0].toLowerCase()];
690
+ // Capitalize first letter of remaining words
691
+ for (let i = 1; i < words.length; i++) {
692
+ const word = words[i];
693
+ if (word) {
694
+ result.push(word.charAt(0).toUpperCase() + word.substring(1).toLowerCase());
695
+ }
747
696
  }
748
- return this.httpUtility.delete(`${this.apiUrl}/${uuid}`);
697
+ return result.join('');
698
+ }
699
+ /**
700
+ * Removes double forward slashes from an endpoint string while preserving the protocol
701
+ * e.g. "http://localhost:8080//api//v1//users" -> "http://localhost:8080/api/v1/users"
702
+ * @param endpoint The endpoint string to clean
703
+ * @returns The cleaned endpoint string without double slashes
704
+ */
705
+ removeDoubleSlashes(endpoint) {
706
+ if (!endpoint)
707
+ return '';
708
+ // Split the URL into protocol and the rest to preserve protocol slashes
709
+ const [protocol, ...rest] = endpoint.split('://');
710
+ if (!rest.length) {
711
+ // If no protocol is present, just clean the entire string
712
+ return endpoint.replace(/\/+/g, '/');
713
+ }
714
+ // Clean the path part (after protocol) and reconstruct the URL
715
+ const cleanPath = rest.join('://').replace(/\/+/g, '/');
716
+ return `${protocol}://${cleanPath}`;
749
717
  }
718
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
719
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UtilsService, providedIn: 'root' });
750
720
  }
721
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: UtilsService, decorators: [{
722
+ type: Injectable,
723
+ args: [{
724
+ providedIn: 'root'
725
+ }]
726
+ }] });
751
727
 
752
- var ComponentContext;
753
- (function (ComponentContext) {
754
- ComponentContext["STANDALONE"] = "STANDALONE";
755
- ComponentContext["TABBED"] = "TABBED";
756
- ComponentContext["MODAL"] = "MODAL";
757
- ComponentContext["EMBEDDED"] = "EMBEDDED";
758
- })(ComponentContext || (ComponentContext = {}));
759
-
760
- var TabbedFormType;
761
- (function (TabbedFormType) {
762
- TabbedFormType["STANDARD"] = "STANDARD";
763
- TabbedFormType["LIST"] = "LIST";
764
- })(TabbedFormType || (TabbedFormType = {}));
728
+ class NotificationService {
729
+ messageService;
730
+ messageSubject = new BehaviorSubject(null);
731
+ message$ = this.messageSubject.asObservable();
732
+ constructor(messageService) {
733
+ this.messageService = messageService;
734
+ }
735
+ showSuccess(message) {
736
+ const msgObj = {
737
+ severity: 'success',
738
+ summary: 'Success',
739
+ detail: message,
740
+ life: 3000
741
+ };
742
+ this.messageSubject.next(msgObj);
743
+ }
744
+ showError(message) {
745
+ const msgObj = {
746
+ severity: 'error',
747
+ summary: 'Error',
748
+ detail: message,
749
+ life: 3000
750
+ };
751
+ this.messageSubject.next(msgObj);
752
+ }
753
+ clearMessage() {
754
+ this.messageSubject.next(null);
755
+ }
756
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NotificationService, deps: [{ token: i2.MessageService }], target: i0.ɵɵFactoryTarget.Injectable });
757
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NotificationService });
758
+ }
759
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NotificationService, decorators: [{
760
+ type: Injectable
761
+ }], ctorParameters: () => [{ type: i2.MessageService }] });
765
762
 
766
- /**
767
- * Base component for all page components in the application.
768
- * Provides common functionality like loading state and message display.
769
- */
770
- class BasePageComponent {
763
+ class BaseCrudService {
764
+ httpUtility;
771
765
  utilsService;
772
- notificationService;
773
- loading = false;
774
- constructor(utilsService, notificationService) {
775
- this.utilsService = utilsService;
776
- this.notificationService = notificationService;
766
+ resourcePath;
767
+ backendBaseUrl;
768
+ messageService;
769
+ // Use the loading state from HttpUtilityService
770
+ get loading$() {
771
+ return this.httpUtility.loading$;
777
772
  }
773
+ apiUrl;
778
774
  /**
779
- * Display an error message to the user
780
- * @param detail The error message to display
775
+ * Creates an instance of BaseCrudService.
776
+ * @param httpUtility - Service for making HTTP requests
777
+ * @param utilsService - Utility service for common operations
778
+ * @param resourcePath - API resource path (e.g., 'customers', 'products')
779
+ * @param backendBaseUrl - Base URL for the backend API
780
+ * @param messageService - Optional service for showing messages
781
781
  */
782
- showError(detail) {
783
- this.notificationService.showError(detail);
782
+ constructor(httpUtility, utilsService, resourcePath, backendBaseUrl, messageService) {
783
+ this.httpUtility = httpUtility;
784
+ this.utilsService = utilsService;
785
+ this.resourcePath = resourcePath;
786
+ this.backendBaseUrl = backendBaseUrl;
787
+ this.messageService = messageService;
788
+ this.apiUrl = this.joinUrls(backendBaseUrl, resourcePath);
784
789
  }
785
790
  /**
786
- * Display a success message to the user
787
- * @param detail The success message to display
791
+ * Joins URL parts ensuring no double slashes
788
792
  */
789
- showSuccess(detail) {
790
- this.notificationService.showSuccess(detail);
793
+ joinUrls(...parts) {
794
+ return parts.map(part => part.replace(/^\/+|\/+$/g, '')).join('/');
791
795
  }
792
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BasePageComponent, deps: [{ token: UtilsService }, { token: NotificationService }], target: i0.ɵɵFactoryTarget.Directive });
793
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: BasePageComponent, isStandalone: true, ngImport: i0 });
794
- }
795
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BasePageComponent, decorators: [{
796
- type: Directive
797
- }], ctorParameters: () => [{ type: UtilsService }, { type: NotificationService }] });
798
-
799
- const AppMessages = {
800
- COMMON: {
801
- ERROR: {
802
- GENERAL: 'An error occurred',
803
- INVALID_INPUT: 'Invalid input provided',
804
- REQUIRED_FIELD: 'This field is required'
805
- },
806
- SUCCESS: {
807
- SAVE: 'saved successfully',
808
- UPDATE: 'updated successfully',
809
- DELETE: 'deleted successfully'
796
+ getAll(pageRequest) {
797
+ if (pageRequest) {
798
+ return this.httpUtility.post(`${this.apiUrl}/page`, pageRequest);
810
799
  }
811
- },
812
- FORM: {
813
- ERROR: {
814
- INIT: 'Failed to initialize form',
815
- SAVE: 'Failed to save',
816
- LOAD: 'Failed to load',
817
- VALIDATION: 'Please fill in all required fields correctly'
818
- },
819
- SUCCESS: {
820
- SAVE: 'saved successfully'
800
+ else {
801
+ return this.httpUtility.get(this.apiUrl);
821
802
  }
822
- },
823
- LIST: {
824
- ERROR: {
825
- LOAD: 'Failed to load',
826
- DELETE: 'Failed to delete',
827
- NAVIGATION: 'Navigation failed',
828
- INVALID_ID: 'Cannot perform action without ID'
829
- },
830
- SUCCESS: {
831
- DELETE: 'deleted successfully'
803
+ }
804
+ getById(uuid) {
805
+ if (!this.utilsService.isValidUUID(uuid)) {
806
+ this.utilsService.showError('Invalid UUID format', this.messageService);
807
+ return throwError(() => new Error('Invalid UUID format'));
832
808
  }
809
+ return this.httpUtility.get(`${this.apiUrl}/${uuid}`);
810
+ }
811
+ create(entity) {
812
+ return this.httpUtility.post(this.apiUrl, entity);
813
+ }
814
+ update(uuid, entity) {
815
+ if (!this.utilsService.isValidUUID(uuid)) {
816
+ this.utilsService.showError('Invalid UUID format', this.messageService);
817
+ return throwError(() => new Error('Invalid UUID format'));
818
+ }
819
+ return this.httpUtility.put(`${this.apiUrl}/${uuid}`, entity);
820
+ }
821
+ delete(uuid) {
822
+ if (!this.utilsService.isValidUUID(uuid)) {
823
+ this.utilsService.showError('Invalid UUID format', this.messageService);
824
+ return throwError(() => new Error('Invalid UUID format'));
825
+ }
826
+ return this.httpUtility.delete(`${this.apiUrl}/${uuid}`);
833
827
  }
834
- };
835
-
836
- class AppConstants {
837
- static FORM = {
838
- MODE: 'mode',
839
- UUID: 'uuid',
840
- NEW: 'new'
841
- };
842
- static PAGE_MODE = {
843
- CREATE: 'create',
844
- EDIT: 'edit',
845
- VIEW: 'view'
846
- };
847
828
  }
848
829
 
849
- class BaseFormComponent extends BasePageComponent {
850
- route;
851
- router;
852
- notificationService;
830
+ /**
831
+ * Base service for task-related operations that extends the BaseCrudService.
832
+ * Implements endpoints defined in Java BaseTaskController.
833
+ */
834
+ class BaseTaskService extends BaseCrudService {
835
+ httpUtility;
853
836
  utilsService;
854
- formlyConfigService;
855
- // form = new FormGroup({});
856
- form = new FormGroup({});
857
- fields = [];
858
- isNew = true;
859
- mode = AppConstants.PAGE_MODE.CREATE;
860
- currentFormData = {};
861
- originalFormData = null;
862
- hasChanges = false;
863
- context = ComponentContext.STANDALONE;
864
- constructor(route, router, notificationService, utilsService, formlyConfigService) {
865
- super(utilsService, notificationService);
866
- this.route = route;
867
- this.router = router;
868
- this.notificationService = notificationService;
837
+ resourcePath;
838
+ backendBaseUrl;
839
+ messageService;
840
+ constructor(httpUtility, utilsService, resourcePath, backendBaseUrl, messageService) {
841
+ super(httpUtility, utilsService, resourcePath, backendBaseUrl, messageService);
842
+ this.httpUtility = httpUtility;
869
843
  this.utilsService = utilsService;
870
- this.formlyConfigService = formlyConfigService;
844
+ this.resourcePath = resourcePath;
845
+ this.backendBaseUrl = backendBaseUrl;
846
+ this.messageService = messageService;
871
847
  }
872
- ngOnInit() {
873
- try {
874
- this.fields = this.getFormlyFieldConfig();
875
- this.form.valueChanges.subscribe(() => {
876
- console.log('[BaseFormComponent] Form value changed:', this.form.value);
877
- this.currentFormData = { ...this.form.value };
878
- this.hasChanges = this.hasFormChanges(this.currentFormData);
879
- });
880
- // Use queryParams instead of params
881
- this.route.queryParams.pipe(take(1)).subscribe({
882
- next: params => this.initializeForm(params),
883
- error: error => {
884
- console.error('Error initializing form:', error);
885
- this.showError(AppMessages.FORM.ERROR.INIT);
886
- }
887
- });
848
+ /**
849
+ * Find tasks by department with pagination
850
+ * Maps to GET /department/{department} endpoint
851
+ */
852
+ findByDepartment(department, pageRequest) {
853
+ if (pageRequest) {
854
+ return this.httpUtility.post(`${this.apiUrl}/department/${department}/page`, pageRequest);
888
855
  }
889
- catch (error) {
890
- console.error('Error in ngOnInit:', error);
891
- this.showError(AppMessages.FORM.ERROR.INIT);
856
+ return this.httpUtility.get(`${this.apiUrl}/department/${department}`);
857
+ }
858
+ /**
859
+ * Find tasks by assigned user with pagination
860
+ * Maps to GET /user/{userId} endpoint
861
+ */
862
+ findByAssignedUser(userId, pageRequest) {
863
+ if (pageRequest) {
864
+ return this.httpUtility.post(`${this.apiUrl}/user/${userId}/page`, pageRequest);
892
865
  }
866
+ return this.httpUtility.get(`${this.apiUrl}/user/${userId}`);
893
867
  }
894
- initializeFormData() {
895
- console.log('[BaseFormComponent] Initializing form data');
896
- console.log('[BaseFormComponent] Current mode:', this.mode);
897
- if (this.mode === AppConstants.PAGE_MODE.CREATE) {
898
- console.log('[BaseFormComponent] Create mode, using defaults');
899
- return;
868
+ /**
869
+ * Send an event for a task
870
+ * Maps to PUT /{uuid}/event/{event} endpoint
871
+ * For ASSIGN events, userId and department are required
872
+ */
873
+ sendEvent(uuid, event, userId, department) {
874
+ if (!this.utilsService.isValidUUID(uuid)) {
875
+ this.utilsService.showError('Invalid UUID format', this.messageService);
876
+ throw new Error('Invalid UUID format');
900
877
  }
901
- const state = {
902
- ...this.router.getCurrentNavigation()?.extras?.state,
903
- ...window.history.state,
904
- ...this.route.snapshot.data
905
- };
906
- console.log('[BaseFormComponent] Combined state:', state);
907
- console.log('[BaseFormComponent] Final state being used:', state);
908
- if (state && 'formData' in state) {
909
- console.log('[BaseFormComponent] Found form data in state:', state['formData']);
910
- this.patchFormData(state['formData']);
878
+ let params = new HttpParams();
879
+ if (userId) {
880
+ params = params.set('userId', userId);
911
881
  }
912
- else {
913
- console.log('[BaseFormComponent] No form data, loading from UUID');
914
- this.loadFromUuid();
882
+ if (department) {
883
+ params = params.set('department', department);
915
884
  }
885
+ return this.httpUtility.put(`${this.apiUrl}/${uuid}/event/${event}`, null, { params });
916
886
  }
917
- loadFromUuid() {
918
- this.route.queryParams.pipe(take(1)).subscribe(params => {
919
- const uuid = params[AppConstants.FORM.UUID];
920
- if (!uuid) {
921
- throw new Error(`UUID is required for ${this.mode} mode when no formData is provided`);
922
- }
923
- this.loadEntity(uuid);
924
- });
887
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTaskService, deps: "invalid", target: i0.ɵɵFactoryTarget.Injectable });
888
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTaskService });
889
+ }
890
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTaskService, decorators: [{
891
+ type: Injectable
892
+ }], ctorParameters: () => [{ type: HttpUtilityService }, { type: UtilsService }, { type: undefined }, { type: undefined }, { type: undefined }] });
893
+
894
+ class EnumRegistryService {
895
+ utilsService;
896
+ enumMap = new Map();
897
+ constructor(utilsService) {
898
+ this.utilsService = utilsService;
925
899
  }
926
- initializeForm(params) {
927
- console.log('[BaseFormComponent] Initializing form with params:', params);
928
- const routeMode = params[AppConstants.FORM.MODE];
929
- this.initializeFormMode(routeMode);
930
- this.setupFormState();
931
- // Check for context in router state
932
- const state = window.history.state;
933
- if (state?.context) {
934
- this.context = state.context;
935
- console.log('[BaseFormComponent] Context set from router state:', this.context);
900
+ /**
901
+ * Initialize global enums
902
+ */
903
+ static registerGlobalEnums(enums) {
904
+ return {
905
+ provide: EnumRegistryService,
906
+ useFactory: (utilsService) => {
907
+ const service = new EnumRegistryService(utilsService);
908
+ Object.entries(enums).forEach(([name, enumType]) => {
909
+ service.register(name, enumType);
910
+ });
911
+ return service;
912
+ },
913
+ deps: [UtilsService]
914
+ };
915
+ }
916
+ /**
917
+ * Register an enum type with the service
918
+ * @param name Name to register the enum under
919
+ * @param enumType The enum type to register
920
+ */
921
+ register(name, enumType) {
922
+ this.enumMap.set(name, enumType);
923
+ }
924
+ /**
925
+ * Get formly-compatible options array for a registered enum
926
+ * @param name Name of the registered enum
927
+ * @returns Array of {label, value} pairs suitable for formly select fields
928
+ */
929
+ getEnumValues(name) {
930
+ const enumType = this.enumMap.get(name);
931
+ if (!enumType) {
932
+ console.warn(`Enum type "${name}" not found in registry`);
933
+ return [];
936
934
  }
937
- this.initializeFormData();
935
+ return this.utilsService.getEnumValues(enumType);
938
936
  }
939
- patchFormData(data) {
940
- console.log('[BaseFormComponent] Patching form data:', data);
941
- if (data) {
942
- if (!this.fields || this.fields.length === 0) {
943
- console.warn('[BaseFormComponent] Fields not initialized yet, reinitializing');
944
- this.fields = this.getFormlyFieldConfig();
937
+ /**
938
+ * Check if an enum type is registered
939
+ * @param name Name of the enum to check
940
+ */
941
+ hasEnum(name) {
942
+ return this.enumMap.has(name);
943
+ }
944
+ /**
945
+ * Clear all registered enums
946
+ */
947
+ clear() {
948
+ this.enumMap.clear();
949
+ }
950
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EnumRegistryService, deps: [{ token: UtilsService }], target: i0.ɵɵFactoryTarget.Injectable });
951
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EnumRegistryService, providedIn: 'root' });
952
+ }
953
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: EnumRegistryService, decorators: [{
954
+ type: Injectable,
955
+ args: [{
956
+ providedIn: 'root'
957
+ }]
958
+ }], ctorParameters: () => [{ type: UtilsService }] });
959
+
960
+ class FormlyConfigService {
961
+ enumRegistry;
962
+ constructor(enumRegistry) {
963
+ this.enumRegistry = enumRegistry;
964
+ }
965
+ getFormConfig(fields, mode, dynamicOptions) {
966
+ const commonConfig = {
967
+ props: {
968
+ disabled: mode === 'view'
969
+ }
970
+ };
971
+ return fields.map((field) => {
972
+ // Apply dynamic options from child component
973
+ if (dynamicOptions && field.key && typeof field.key === 'string') {
974
+ const fieldOptions = dynamicOptions[field.key];
975
+ if (fieldOptions) {
976
+ field.props = {
977
+ ...field.props,
978
+ ...fieldOptions
979
+ };
980
+ }
945
981
  }
946
- this.currentFormData = { ...data };
947
- this.originalFormData = data;
948
- this.isNew = false;
949
- console.log('[BaseFormComponent] Form data updated:', this.currentFormData);
950
- console.log('[BaseFormComponent] Fields available:', this.fields);
951
- setTimeout(() => {
952
- if (!this.form.valid) {
953
- console.warn('[BaseFormComponent] Form validation issues after data patch');
982
+ // Handle field groups recursively
983
+ if (field.fieldGroup) {
984
+ field.fieldGroup = this.getFormConfig(field.fieldGroup, mode, dynamicOptions);
985
+ }
986
+ return {
987
+ ...field,
988
+ ...commonConfig,
989
+ props: {
990
+ ...field.props,
991
+ ...commonConfig.props
954
992
  }
955
- console.log('[BaseFormComponent] Form state after sync:', {
956
- valid: this.form.valid,
957
- values: this.form.value,
958
- currentData: this.currentFormData
959
- });
960
- }, 100);
993
+ };
994
+ });
995
+ }
996
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormlyConfigService, deps: [{ token: EnumRegistryService }], target: i0.ɵɵFactoryTarget.Injectable });
997
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormlyConfigService, providedIn: 'root' });
998
+ }
999
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormlyConfigService, decorators: [{
1000
+ type: Injectable,
1001
+ args: [{
1002
+ providedIn: 'root'
1003
+ }]
1004
+ }], ctorParameters: () => [{ type: EnumRegistryService }] });
1005
+
1006
+ class FormService {
1007
+ entityService;
1008
+ formlyConfigService;
1009
+ notificationService;
1010
+ entityName;
1011
+ jsonFields;
1012
+ dropdownOptions;
1013
+ formState;
1014
+ constructor(entityService, formlyConfigService, notificationService, entityName, jsonFields, dropdownOptions = {}) {
1015
+ this.entityService = entityService;
1016
+ this.formlyConfigService = formlyConfigService;
1017
+ this.notificationService = notificationService;
1018
+ this.entityName = entityName;
1019
+ this.jsonFields = jsonFields;
1020
+ this.dropdownOptions = dropdownOptions;
1021
+ this.formState = {
1022
+ form: new FormGroup({}),
1023
+ fields: [],
1024
+ isNew: true,
1025
+ mode: AppConstants.PAGE_MODE.CREATE,
1026
+ currentData: {},
1027
+ originalData: null,
1028
+ hasChanges: false,
1029
+ loading: false,
1030
+ };
1031
+ this.initializeForm();
1032
+ }
1033
+ initializeForm() {
1034
+ this.formState.fields = this.getFormlyFieldConfig();
1035
+ if (this.formState.mode === AppConstants.PAGE_MODE.VIEW) {
1036
+ this.formState.form.disable();
961
1037
  }
962
1038
  }
963
- initializeFormMode(routeMode) {
964
- if (!routeMode) {
965
- this.mode = AppConstants.PAGE_MODE.CREATE;
1039
+ getFormlyFieldConfig() {
1040
+ return this.formlyConfigService.getFormConfig(this.jsonFields, this.formState.mode, this.dropdownOptions);
1041
+ }
1042
+ loadData(uuid) {
1043
+ if (!uuid || this.formState.mode === AppConstants.PAGE_MODE.CREATE) {
1044
+ return of(void 0);
1045
+ }
1046
+ this.formState.loading = true;
1047
+ return this.entityService.getById(uuid).pipe(take(1), map(data => {
1048
+ this.updateFormState(data);
1049
+ }), finalize$1(() => this.formState.loading = false));
1050
+ }
1051
+ updateFormState(data) {
1052
+ this.formState = {
1053
+ ...this.formState,
1054
+ currentData: { ...data },
1055
+ originalData: data,
1056
+ isNew: false,
1057
+ loading: false
1058
+ };
1059
+ this.formState.form.patchValue(data, { emitEvent: false });
1060
+ }
1061
+ submit() {
1062
+ if (!this.validateSubmission()) {
1063
+ return of(null);
966
1064
  }
967
- else if (this.isValidPageMode(routeMode)) {
968
- this.mode = routeMode;
1065
+ this.formState.loading = true;
1066
+ const operation$ = this.getSubmitOperation();
1067
+ return operation$.pipe(finalize$1(() => this.formState.loading = false), map(result => {
1068
+ this.notificationService.showSuccess(`${this.entityName} ${AppMessages.FORM.SUCCESS.SAVE}`);
1069
+ return result;
1070
+ }));
1071
+ }
1072
+ validateSubmission() {
1073
+ if (!this.formState.form.valid) {
1074
+ this.notificationService.showError('Please check form errors');
1075
+ return false;
969
1076
  }
970
- else {
971
- throw new Error(`Invalid page mode: ${routeMode}`);
1077
+ if (!this.formState.hasChanges && !this.formState.isNew) {
1078
+ this.notificationService.showSuccess('No changes to save');
1079
+ return false;
1080
+ }
1081
+ if (!this.formState.currentData || Object.keys(this.formState.currentData).length === 0) {
1082
+ this.notificationService.showError('No data to save');
1083
+ return false;
972
1084
  }
1085
+ return true;
973
1086
  }
974
- setupFormState() {
975
- console.log('[BaseFormComponent] Setting up form state, mode:', this.mode);
976
- this.fields = this.getFormlyFieldConfig();
977
- if (this.mode === AppConstants.PAGE_MODE.CREATE) {
978
- console.log('[BaseFormComponent] Create mode setup');
979
- this.isNew = true;
980
- this.loading = false;
981
- this.originalFormData = null;
1087
+ getSubmitOperation() {
1088
+ const formData = this.formState.currentData;
1089
+ return this.formState.isNew
1090
+ ? this.entityService.create(formData)
1091
+ : this.entityService.update(this.formState.originalData.uuid, formData);
1092
+ }
1093
+ setMode(mode) {
1094
+ this.formState.mode = mode;
1095
+ if (mode === AppConstants.PAGE_MODE.VIEW) {
1096
+ this.formState.form.disable();
982
1097
  }
983
- if (this.mode === AppConstants.PAGE_MODE.VIEW) {
984
- console.log('[BaseFormComponent] View mode setup');
985
- this.form.disable();
1098
+ else {
1099
+ this.formState.form.enable();
986
1100
  }
987
1101
  }
988
- getFormlyFieldConfig() {
989
- return this.formlyConfigService.getFormConfig(this.getJsonFields(), this.mode, this.getDropdownOptions());
1102
+ getState() {
1103
+ return this.formState;
990
1104
  }
991
- onSubmitRoute() {
992
- return [`/${this.getEntityName().toLowerCase()}s`];
1105
+ patchValue(value) {
1106
+ this.formState.form.patchValue(value);
1107
+ this.updateCurrentData();
1108
+ this.checkFormChanges();
993
1109
  }
994
- getEntityOperations() {
995
- const entityService = this.getEntityService();
996
- return {
997
- create: (data) => entityService.create(data),
998
- update: (uuid, data) => entityService.update(uuid, { ...data, uuid }),
999
- getById: (uuid) => entityService.getById(uuid)
1110
+ updateCurrentData() {
1111
+ this.formState.currentData = {
1112
+ ...this.formState.form.value,
1113
+ uuid: this.formState.originalData?.uuid
1000
1114
  };
1001
1115
  }
1002
- onSubmit() {
1003
- console.log('[BaseFormComponent] Form submission:', {
1004
- formValid: this.form.valid,
1005
- formData: this.currentFormData,
1006
- formState: this.form.value
1007
- });
1008
- if (!this.currentFormData || Object.keys(this.currentFormData).length === 0) {
1009
- this.notificationService.showError('No data to save');
1010
- return;
1011
- }
1012
- if (this.form.valid) {
1013
- if (this.isNew) {
1014
- console.log('[BaseFormComponent] Saving new entity');
1015
- this.saveEntity();
1016
- return;
1017
- }
1018
- const hasChanges = this.hasFormChanges(this.currentFormData);
1019
- console.log('[BaseFormComponent] Change detection:', {
1020
- hasChanges,
1021
- currentData: this.currentFormData,
1022
- originalData: this.originalFormData
1023
- });
1024
- if (!hasChanges) {
1025
- this.notificationService.showSuccess('No changes to save');
1026
- return;
1027
- }
1028
- console.log('[BaseFormComponent] Saving changes');
1029
- this.saveEntity();
1030
- }
1031
- else {
1032
- console.warn('[BaseFormComponent] Form validation failed:', this.form.errors);
1033
- this.notificationService.showError('Please check form errors');
1034
- }
1116
+ checkFormChanges() {
1117
+ this.formState.hasChanges = this.hasFormChanges(this.formState.currentData);
1035
1118
  }
1036
1119
  hasFormChanges(currentData) {
1037
- if (!this.originalFormData || this.isNew) {
1120
+ if (!this.formState.originalData || this.formState.isNew) {
1038
1121
  return true;
1039
1122
  }
1040
- console.log('[BaseFormComponent] Checking for changes:', {
1041
- currentData,
1042
- originalData: this.originalFormData
1043
- });
1044
- const hasChanges = Object.keys(currentData).some(key => {
1123
+ return Object.keys(currentData).some(key => {
1045
1124
  const currentValue = currentData[key];
1046
- const originalValue = this.originalFormData[key];
1047
- const changed = currentValue !== originalValue;
1048
- if (changed) {
1049
- console.log(`[BaseFormComponent] Field '${key}' changed:`, {
1050
- from: originalValue,
1051
- to: currentValue
1052
- });
1053
- }
1054
- return changed;
1055
- });
1056
- console.log('[BaseFormComponent] Changes detected:', hasChanges);
1057
- return hasChanges;
1058
- }
1059
- saveEntity() {
1060
- this.loading = true;
1061
- const formData = this.currentFormData;
1062
- const entityName = this.getEntityName();
1063
- const entityOperations = this.getEntityOperations();
1064
- const request$ = this.isNew
1065
- ? entityOperations.create(formData)
1066
- : entityOperations.update(this.originalFormData.uuid, formData);
1067
- request$.subscribe({
1068
- next: () => {
1069
- this.loading = false;
1070
- this.notificationService.showSuccess(`${entityName} ${AppMessages.FORM.SUCCESS.SAVE}`);
1071
- this.router.navigate(this.onSubmitRoute());
1072
- },
1073
- error: (error) => {
1074
- const errorMessage = error.error?.message || `${AppMessages.FORM.ERROR.SAVE} ${entityName}`;
1075
- this.showError(errorMessage);
1076
- this.loading = false;
1077
- }
1078
- });
1079
- }
1080
- loadEntity(uuid) {
1081
- this.loading = true;
1082
- this.isNew = false;
1083
- const entityName = this.getEntityName();
1084
- this.getEntityOperations().getById(uuid).subscribe({
1085
- next: (data) => {
1086
- this.originalFormData = data;
1087
- this.currentFormData = { ...data };
1088
- this.loading = false;
1089
- console.log('[BaseFormComponent] Original data loaded:', this.originalFormData);
1090
- console.log('[BaseFormComponent] Form data updated:', this.currentFormData);
1091
- },
1092
- error: () => {
1093
- this.showError(`${AppMessages.FORM.ERROR.LOAD} ${entityName}`);
1094
- this.loading = false;
1095
- }
1125
+ const originalValue = this.formState.originalData[key];
1126
+ return currentValue !== originalValue;
1096
1127
  });
1097
1128
  }
1098
- isValidPageMode(mode) {
1099
- return Object.values(AppConstants.PAGE_MODE).includes(mode);
1100
- }
1101
- getDropdownOptions() {
1102
- return {};
1103
- }
1104
- /**
1105
- * Checks if the component is running in a specific context
1106
- * @param contextType The context type to check against
1107
- */
1108
- isInContext(contextType) {
1109
- return this.context === contextType;
1110
- }
1111
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseFormComponent, deps: [{ token: i1$2.ActivatedRoute }, { token: i1$2.Router }, { token: NotificationService }, { token: UtilsService }, { token: FormlyConfigService }], target: i0.ɵɵFactoryTarget.Component });
1112
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: BaseFormComponent, isStandalone: true, selector: "ng-component", inputs: { context: "context" }, usesInheritance: true, ngImport: i0, template: '', isInline: true });
1113
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseFormComponent });
1114
1129
  }
1115
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseFormComponent, decorators: [{
1116
- type: Injectable
1117
- }, {
1130
+
1131
+ class TextInputType extends FieldType {
1132
+ defaultOptions = {
1133
+ props: {
1134
+ type: 'text',
1135
+ placeholder: ''
1136
+ }
1137
+ };
1138
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextInputType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1139
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: TextInputType, isStandalone: true, selector: "formly-field-text-input", usesInheritance: true, ngImport: i0, template: `
1140
+ <input pInputText
1141
+ [type]="props['type'] || 'text'"
1142
+ [formControl]="$any(formControl)"
1143
+ [formlyAttributes]="field"
1144
+ [placeholder]="props['placeholder'] || ''"
1145
+ [attr.aria-label]="props['label'] || props['placeholder'] || ''"
1146
+ class="w-full"/>
1147
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2$1.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] });
1148
+ }
1149
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextInputType, decorators: [{
1118
1150
  type: Component,
1119
1151
  args: [{
1120
- template: ''
1121
- }]
1122
- }], ctorParameters: () => [{ type: i1$2.ActivatedRoute }, { type: i1$2.Router }, { type: NotificationService }, { type: UtilsService }, { type: FormlyConfigService }], propDecorators: { context: [{
1123
- type: Input
1124
- }] } });
1125
-
1126
- /**
1127
- * Base component for all list page components in the application.
1128
- * Provides common functionality for list operations like loading data,
1129
- * pagination, sorting, and CRUD operations.
1130
- */
1131
- class BaseListPageComponent extends BasePageComponent {
1132
- router;
1133
- notificationService;
1134
- utilsService;
1135
- confirmationService;
1136
- table;
1137
- items = [];
1138
- actionLoading = false;
1152
+ selector: 'formly-field-text-input',
1153
+ standalone: true,
1154
+ imports: [
1155
+ CommonModule,
1156
+ ReactiveFormsModule,
1157
+ FormlyModule,
1158
+ InputTextModule
1159
+ ],
1160
+ template: `
1161
+ <input pInputText
1162
+ [type]="props['type'] || 'text'"
1163
+ [formControl]="$any(formControl)"
1164
+ [formlyAttributes]="field"
1165
+ [placeholder]="props['placeholder'] || ''"
1166
+ [attr.aria-label]="props['label'] || props['placeholder'] || ''"
1167
+ class="w-full"/>
1168
+ `
1169
+ }]
1170
+ }] });
1171
+
1172
+ class SelectType extends FieldType {
1173
+ get options$() {
1174
+ const options = this.props['options'];
1175
+ return isObservable(options) ? options : of(options || []);
1176
+ }
1177
+ defaultOptions = {
1178
+ props: {
1179
+ options: [],
1180
+ optionLabel: 'label',
1181
+ optionValue: 'value',
1182
+ placeholder: 'Select',
1183
+ showClear: false,
1184
+ filter: false,
1185
+ virtualScroll: false
1186
+ }
1187
+ };
1188
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: SelectType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1189
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: SelectType, isStandalone: true, selector: "formly-field-select", usesInheritance: true, ngImport: i0, template: `
1190
+ <ng-container *ngIf="options$ | async as options">
1191
+ <p-select
1192
+ [formControl]="$any(formControl)"
1193
+ [formlyAttributes]="field"
1194
+ [options]="options"
1195
+ [virtualScroll]="false"
1196
+ [showClear]="false"
1197
+ [placeholder]="props['placeholder'] || 'Select'"
1198
+ styleClass="w-full">
1199
+ </p-select>
1200
+ </ng-container>
1201
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$3.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2$1.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "size", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] });
1202
+ }
1203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: SelectType, decorators: [{
1204
+ type: Component,
1205
+ args: [{
1206
+ selector: 'formly-field-select',
1207
+ standalone: true,
1208
+ imports: [
1209
+ CommonModule,
1210
+ ReactiveFormsModule,
1211
+ FormlyModule,
1212
+ SelectModule
1213
+ ],
1214
+ template: `
1215
+ <ng-container *ngIf="options$ | async as options">
1216
+ <p-select
1217
+ [formControl]="$any(formControl)"
1218
+ [formlyAttributes]="field"
1219
+ [options]="options"
1220
+ [virtualScroll]="false"
1221
+ [showClear]="false"
1222
+ [placeholder]="props['placeholder'] || 'Select'"
1223
+ styleClass="w-full">
1224
+ </p-select>
1225
+ </ng-container>
1226
+ `
1227
+ }]
1228
+ }] });
1229
+
1230
+ class RadioType extends FieldType {
1231
+ get options$() {
1232
+ const options = this.props['options'];
1233
+ return isObservable(options) ? options : of(options || []);
1234
+ }
1235
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RadioType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1236
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: RadioType, isStandalone: true, selector: "formly-field-radio", usesInheritance: true, ngImport: i0, template: `
1237
+ <div class="flex flex-column gap-1">
1238
+ <ng-container *ngIf="options$ | async as options">
1239
+ <div *ngFor="let option of options; let i = index" class="flex align-items-center gap-2">
1240
+ <p-radioButton
1241
+ [name]="field.name || ''"
1242
+ [value]="option.value"
1243
+ [formControl]="$any(formControl)"
1244
+ [formlyAttributes]="field"
1245
+ [size]="'small'"
1246
+ [inputId]="field.key + '_' + i">
1247
+ </p-radioButton>
1248
+ <label [for]="field.key + '_' + i" class="p-radio-label cursor-pointer">
1249
+ {{option.label}}
1250
+ </label>
1251
+ </div>
1252
+ </ng-container>
1253
+ </div>
1254
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$3.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2$1.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i4$1.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "formControlName", "name", "disabled", "variant", "size", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "style", "styleClass", "autofocus", "binary"], outputs: ["onClick", "onFocus", "onBlur"] }] });
1255
+ }
1256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RadioType, decorators: [{
1257
+ type: Component,
1258
+ args: [{
1259
+ selector: 'formly-field-radio',
1260
+ standalone: true,
1261
+ imports: [
1262
+ CommonModule,
1263
+ ReactiveFormsModule,
1264
+ FormlyModule,
1265
+ RadioButtonModule
1266
+ ],
1267
+ template: `
1268
+ <div class="flex flex-column gap-1">
1269
+ <ng-container *ngIf="options$ | async as options">
1270
+ <div *ngFor="let option of options; let i = index" class="flex align-items-center gap-2">
1271
+ <p-radioButton
1272
+ [name]="field.name || ''"
1273
+ [value]="option.value"
1274
+ [formControl]="$any(formControl)"
1275
+ [formlyAttributes]="field"
1276
+ [size]="'small'"
1277
+ [inputId]="field.key + '_' + i">
1278
+ </p-radioButton>
1279
+ <label [for]="field.key + '_' + i" class="p-radio-label cursor-pointer">
1280
+ {{option.label}}
1281
+ </label>
1282
+ </div>
1283
+ </ng-container>
1284
+ </div>
1285
+ `
1286
+ }]
1287
+ }] });
1288
+
1289
+ class NumericInputType extends FieldType {
1290
+ defaultOptions = {
1291
+ props: {
1292
+ min: undefined,
1293
+ max: undefined,
1294
+ minFractionDigits: 0,
1295
+ maxFractionDigits: 0,
1296
+ prefix: '',
1297
+ suffix: '',
1298
+ useGrouping: true
1299
+ }
1300
+ };
1301
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NumericInputType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1302
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: NumericInputType, isStandalone: true, selector: "formly-field-numeric-input", usesInheritance: true, ngImport: i0, template: `
1303
+ <p-inputNumber
1304
+ [formControl]="$any(formControl)"
1305
+ [formlyAttributes]="field"
1306
+ [placeholder]="props['placeholder'] || ''"
1307
+ [min]="props['min'] || 0"
1308
+ [max]="props['max']"
1309
+ [minFractionDigits]="props['minFractionDigits'] || 0"
1310
+ [maxFractionDigits]="props['maxFractionDigits'] || 0"
1311
+ [prefix]="props['prefix'] || ''"
1312
+ [suffix]="props['suffix'] || ''"
1313
+ [useGrouping]="props['useGrouping'] ?? true"
1314
+ [showButtons]="true"
1315
+ class="w-full">
1316
+ </p-inputNumber>
1317
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2$1.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: InputNumberModule }, { kind: "component", type: i3$1.InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "style", "placeholder", "size", "maxlength", "tabindex", "title", "ariaLabelledBy", "ariaLabel", "ariaRequired", "name", "required", "autocomplete", "min", "max", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "step", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "variant", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus", "disabled", "fluid"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }] });
1318
+ }
1319
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NumericInputType, decorators: [{
1320
+ type: Component,
1321
+ args: [{
1322
+ selector: 'formly-field-numeric-input',
1323
+ standalone: true,
1324
+ imports: [
1325
+ CommonModule,
1326
+ ReactiveFormsModule,
1327
+ FormlyModule,
1328
+ InputNumberModule
1329
+ ],
1330
+ template: `
1331
+ <p-inputNumber
1332
+ [formControl]="$any(formControl)"
1333
+ [formlyAttributes]="field"
1334
+ [placeholder]="props['placeholder'] || ''"
1335
+ [min]="props['min'] || 0"
1336
+ [max]="props['max']"
1337
+ [minFractionDigits]="props['minFractionDigits'] || 0"
1338
+ [maxFractionDigits]="props['maxFractionDigits'] || 0"
1339
+ [prefix]="props['prefix'] || ''"
1340
+ [suffix]="props['suffix'] || ''"
1341
+ [useGrouping]="props['useGrouping'] ?? true"
1342
+ [showButtons]="true"
1343
+ class="w-full">
1344
+ </p-inputNumber>
1345
+ `
1346
+ }]
1347
+ }] });
1348
+
1349
+ class MultiselectType extends FieldType {
1350
+ get options$() {
1351
+ const options = this.props['options'];
1352
+ return isObservable(options) ? options : of(options || []);
1353
+ }
1354
+ defaultOptions = {
1355
+ props: {
1356
+ options: [],
1357
+ optionLabel: 'label',
1358
+ optionValue: 'value',
1359
+ placeholder: 'Select',
1360
+ filter: false,
1361
+ maxSelectedLabels: 3,
1362
+ showToggleAll: true,
1363
+ virtualScroll: false
1364
+ }
1365
+ };
1366
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MultiselectType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1367
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: MultiselectType, isStandalone: true, selector: "formly-field-multiselect", usesInheritance: true, ngImport: i0, template: `
1368
+ <ng-container *ngIf="options$ | async as options">
1369
+ <p-multiSelect
1370
+ [formControl]="$any(formControl)"
1371
+ [formlyAttributes]="field"
1372
+ [options]="options"
1373
+ [optionLabel]="props['optionLabel'] || 'label'"
1374
+ [optionValue]="props['optionValue'] || 'value'"
1375
+ [placeholder]="props['placeholder'] || 'Select'"
1376
+ [filter]="props['filter'] || true"
1377
+ [filterBy]="props['filterBy'] || 'label'"
1378
+ [maxSelectedLabels]="props['maxSelectedLabels'] || 2"
1379
+ [showToggleAll]="props['showToggleAll'] || true"
1380
+ [virtualScroll]="props['virtualScroll'] || false"
1381
+ [display]="props['display'] || 'chip'"
1382
+ styleClass="w-full">
1383
+ </p-multiSelect>
1384
+ </ng-container>
1385
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$3.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2$1.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i4$2.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "style", "styleClass", "panelStyle", "panelStyleClass", "inputId", "disabled", "fluid", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "variant", "appendTo", "dataKey", "name", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "size", "showClear", "autofocus", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "defaultLabel", "placeholder", "options", "filterValue", "itemSize", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }] });
1386
+ }
1387
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MultiselectType, decorators: [{
1388
+ type: Component,
1389
+ args: [{
1390
+ selector: 'formly-field-multiselect',
1391
+ standalone: true,
1392
+ imports: [
1393
+ CommonModule,
1394
+ ReactiveFormsModule,
1395
+ FormlyModule,
1396
+ MultiSelectModule
1397
+ ],
1398
+ template: `
1399
+ <ng-container *ngIf="options$ | async as options">
1400
+ <p-multiSelect
1401
+ [formControl]="$any(formControl)"
1402
+ [formlyAttributes]="field"
1403
+ [options]="options"
1404
+ [optionLabel]="props['optionLabel'] || 'label'"
1405
+ [optionValue]="props['optionValue'] || 'value'"
1406
+ [placeholder]="props['placeholder'] || 'Select'"
1407
+ [filter]="props['filter'] || true"
1408
+ [filterBy]="props['filterBy'] || 'label'"
1409
+ [maxSelectedLabels]="props['maxSelectedLabels'] || 2"
1410
+ [showToggleAll]="props['showToggleAll'] || true"
1411
+ [virtualScroll]="props['virtualScroll'] || false"
1412
+ [display]="props['display'] || 'chip'"
1413
+ styleClass="w-full">
1414
+ </p-multiSelect>
1415
+ </ng-container>
1416
+ `
1417
+ }]
1418
+ }] });
1419
+
1420
+ class DateType extends FieldType {
1421
+ defaultOptions = {
1422
+ props: {
1423
+ dateFormat: 'dd-mm-yy',
1424
+ showTime: false,
1425
+ touchUI: false,
1426
+ placeholder: '',
1427
+ showButtonBar: true,
1428
+ }
1429
+ };
1430
+ onDateSelect(event) {
1431
+ this.formatAndSetDate(event);
1432
+ }
1433
+ onDateInput(event) {
1434
+ const inputValue = event.target.value.trim();
1435
+ if (this.isValidDateInput(inputValue, this.props['dateFormat'] || 'dd-mm-yy')) {
1436
+ const parsedDate = this.parseDate(inputValue, this.props['dateFormat'] || 'dd-mm-yy');
1437
+ if (parsedDate) {
1438
+ this.formatAndSetDate(parsedDate);
1439
+ }
1440
+ }
1441
+ else {
1442
+ this.formControl?.setErrors({ invalidDate: true });
1443
+ }
1444
+ }
1445
+ formatAndSetDate(date) {
1446
+ if (this.formControl) {
1447
+ const formattedDate = formatDate(date, 'dd-MM-yyyy', 'en-US');
1448
+ this.formControl.setValue(formattedDate);
1449
+ }
1450
+ }
1451
+ parseDate(value, format) {
1452
+ const parts = value.split(/[-/]/); // Split by common delimiters
1453
+ if (parts.length === 3) {
1454
+ const [day, month, year] = format === 'dd-mm-yy'
1455
+ ? [parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10)]
1456
+ : [parseInt(parts[1], 10), parseInt(parts[0], 10) - 1, parseInt(parts[2], 10)];
1457
+ return new Date(year, month, day);
1458
+ }
1459
+ return null;
1460
+ }
1461
+ isValidDateInput(value, format) {
1462
+ const dateRegex = format === 'dd-mm-yy'
1463
+ ? /^\d{2}-\d{2}-\d{4}$/
1464
+ : /^\d{2}\/\d{2}\/\d{4}$/;
1465
+ return dateRegex.test(value);
1466
+ }
1467
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DateType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1468
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: DateType, isStandalone: true, selector: "formly-field-date", usesInheritance: true, ngImport: i0, template: `
1469
+ <p-datepicker
1470
+ [formControl]="$any(formControl)"
1471
+ [formlyAttributes]="field"
1472
+ [showIcon]="true"
1473
+ [dateFormat]="props['dateFormat'] || 'dd-mm-yy'"
1474
+ [showTime]="props['showTime'] || false"
1475
+ [touchUI]="props['touchUI'] || false"
1476
+ [placeholder]="props['placeholder'] || ''"
1477
+ [minDate]="props['minDate']"
1478
+ [maxDate]="props['maxDate']"
1479
+ [showOnFocus]="false"
1480
+ [todayButtonStyleClass]="'p-button-secondary'"
1481
+ [clearButtonStyleClass]="'p-button-secondary'"
1482
+ styleClass="w-full"
1483
+ (onSelect)="onDateSelect($event)"
1484
+ (onInput)="onDateInput($event)">
1485
+ </p-datepicker>
1486
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2$1.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i3$2.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "style", "styleClass", "inputStyle", "inputId", "name", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "disabled", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "fluid", "icon", "appendTo", "readonlyInput", "shortYearCutoff", "monthNavigator", "yearNavigator", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "required", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "variant", "size", "minDate", "maxDate", "disabledDates", "disabledDays", "yearRange", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "locale", "view", "defaultDate"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }] });
1487
+ }
1488
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DateType, decorators: [{
1489
+ type: Component,
1490
+ args: [{
1491
+ selector: 'formly-field-date',
1492
+ standalone: true,
1493
+ imports: [
1494
+ CommonModule,
1495
+ ReactiveFormsModule,
1496
+ FormlyModule,
1497
+ DatePickerModule
1498
+ ],
1499
+ template: `
1500
+ <p-datepicker
1501
+ [formControl]="$any(formControl)"
1502
+ [formlyAttributes]="field"
1503
+ [showIcon]="true"
1504
+ [dateFormat]="props['dateFormat'] || 'dd-mm-yy'"
1505
+ [showTime]="props['showTime'] || false"
1506
+ [touchUI]="props['touchUI'] || false"
1507
+ [placeholder]="props['placeholder'] || ''"
1508
+ [minDate]="props['minDate']"
1509
+ [maxDate]="props['maxDate']"
1510
+ [showOnFocus]="false"
1511
+ [todayButtonStyleClass]="'p-button-secondary'"
1512
+ [clearButtonStyleClass]="'p-button-secondary'"
1513
+ styleClass="w-full"
1514
+ (onSelect)="onDateSelect($event)"
1515
+ (onInput)="onDateInput($event)">
1516
+ </p-datepicker>
1517
+ `
1518
+ }]
1519
+ }] });
1520
+
1521
+ class CheckboxType extends FieldType {
1522
+ defaultOptions = {
1523
+ props: {
1524
+ trueValue: true,
1525
+ falseValue: false
1526
+ }
1527
+ };
1528
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CheckboxType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1529
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: CheckboxType, isStandalone: true, selector: "formly-field-checkbox", usesInheritance: true, ngImport: i0, template: `
1530
+ <div class="checkbox-container">
1531
+ <p-checkbox
1532
+ [formControl]="$any(formControl)"
1533
+ [formlyAttributes]="field"
1534
+ [binary]="!props['options']"
1535
+ [trueValue]="props['trueValue']"
1536
+ [falseValue]="props['falseValue']">
1537
+ {{props['checkboxLabel'] || ''}}
1538
+ </p-checkbox>
1539
+ </div>
1540
+ `, isInline: true, styles: [".checkbox-container{display:flex;align-items:center;height:40px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2$1.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i3$3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }] });
1541
+ }
1542
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CheckboxType, decorators: [{
1543
+ type: Component,
1544
+ args: [{ selector: 'formly-field-checkbox', standalone: true, imports: [
1545
+ CommonModule,
1546
+ ReactiveFormsModule,
1547
+ FormlyModule,
1548
+ CheckboxModule
1549
+ ], template: `
1550
+ <div class="checkbox-container">
1551
+ <p-checkbox
1552
+ [formControl]="$any(formControl)"
1553
+ [formlyAttributes]="field"
1554
+ [binary]="!props['options']"
1555
+ [trueValue]="props['trueValue']"
1556
+ [falseValue]="props['falseValue']">
1557
+ {{props['checkboxLabel'] || ''}}
1558
+ </p-checkbox>
1559
+ </div>
1560
+ `, styles: [".checkbox-container{display:flex;align-items:center;height:40px}\n"] }]
1561
+ }] });
1562
+
1563
+ class TextareaType extends FieldType {
1564
+ defaultOptions = {
1565
+ props: {
1566
+ rows: 3,
1567
+ cols: undefined,
1568
+ autoResize: true,
1569
+ placeholder: ''
1570
+ }
1571
+ };
1572
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextareaType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1573
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: TextareaType, isStandalone: true, selector: "formly-field-textarea", usesInheritance: true, ngImport: i0, template: `
1574
+ <textarea pInputTextarea
1575
+ [formControl]="$any(formControl)"
1576
+ [formlyAttributes]="field"
1577
+ [rows]="props['rows'] || 3"
1578
+ [cols]="props['cols']"
1579
+ [autoResize]="props['autoResize']"
1580
+ [placeholder]="props['placeholder'] || ''"
1581
+ class="w-full">
1582
+ </textarea>
1583
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2$1.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: Textarea }, { kind: "directive", type: i3$4.InputTextarea, selector: "[pInputTextarea]", inputs: ["autoResize", "variant", "fluid"], outputs: ["onResize"] }] });
1584
+ }
1585
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextareaType, decorators: [{
1586
+ type: Component,
1587
+ args: [{
1588
+ selector: 'formly-field-textarea',
1589
+ standalone: true,
1590
+ imports: [
1591
+ CommonModule,
1592
+ ReactiveFormsModule,
1593
+ FormlyModule,
1594
+ Textarea
1595
+ ],
1596
+ template: `
1597
+ <textarea pInputTextarea
1598
+ [formControl]="$any(formControl)"
1599
+ [formlyAttributes]="field"
1600
+ [rows]="props['rows'] || 3"
1601
+ [cols]="props['cols']"
1602
+ [autoResize]="props['autoResize']"
1603
+ [placeholder]="props['placeholder'] || ''"
1604
+ class="w-full">
1605
+ </textarea>
1606
+ `
1607
+ }]
1608
+ }] });
1609
+
1610
+ class FormFieldWrapper extends FieldWrapper {
1611
+ get errorMessage() {
1612
+ if (!this.field.formControl?.errors) {
1613
+ return '';
1614
+ }
1615
+ const firstErrorKey = Object.keys(this.field.formControl.errors)[0];
1616
+ return this.props.validation?.messages?.[firstErrorKey] || 'This field is required';
1617
+ }
1618
+ get showError() {
1619
+ if (!this.field.formControl) {
1620
+ return false;
1621
+ }
1622
+ const { touched, dirty, invalid } = this.field.formControl;
1623
+ return invalid && (touched || dirty);
1624
+ }
1625
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormFieldWrapper, deps: null, target: i0.ɵɵFactoryTarget.Component });
1626
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: FormFieldWrapper, isStandalone: true, selector: "formly-wrapper-form-field", usesInheritance: true, ngImport: i0, template: `
1627
+ <div class="field mb-4">
1628
+ <label *ngIf="props.label" [for]="id" class="font-medium">
1629
+ {{ props.label }}
1630
+ <span class="required-star" *ngIf="props.required">*</span>
1631
+ <i *ngIf="props.tooltip"
1632
+ class="pi pi-info-circle ml-2"
1633
+ [pTooltip]="props.tooltip">
1634
+ </i>
1635
+ </label>
1636
+
1637
+ <div [class.p-input-error]="showError">
1638
+ <ng-container #fieldComponent></ng-container>
1639
+ </div>
1640
+
1641
+ <small *ngIf="props.description" class="block mt-1">
1642
+ {{ props.description }}
1643
+ </small>
1644
+
1645
+ <small *ngIf="showError" class="p-error block">
1646
+ <i class="pi pi-exclamation-circle mr-2"></i>
1647
+ {{ errorMessage }}
1648
+ </small>
1649
+ </div>
1650
+ `, isInline: true, styles: [":is() .required-star{color:var(--red-500);margin-left:.25rem}:is() .p-input-error :where(input,.p-inputtext){border-color:var(--red-500)}:is() .p-input-error :where(input:enabled:focus,.p-inputtext:enabled:focus){border-color:var(--red-500);box-shadow:0 0 0 1px var(--red-500)}:is() .p-error{color:var(--red-500);margin-top:.5rem}:is() small{color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2$2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] });
1651
+ }
1652
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormFieldWrapper, decorators: [{
1653
+ type: Component,
1654
+ args: [{ selector: 'formly-wrapper-form-field', standalone: true, imports: [CommonModule, TooltipModule], template: `
1655
+ <div class="field mb-4">
1656
+ <label *ngIf="props.label" [for]="id" class="font-medium">
1657
+ {{ props.label }}
1658
+ <span class="required-star" *ngIf="props.required">*</span>
1659
+ <i *ngIf="props.tooltip"
1660
+ class="pi pi-info-circle ml-2"
1661
+ [pTooltip]="props.tooltip">
1662
+ </i>
1663
+ </label>
1664
+
1665
+ <div [class.p-input-error]="showError">
1666
+ <ng-container #fieldComponent></ng-container>
1667
+ </div>
1668
+
1669
+ <small *ngIf="props.description" class="block mt-1">
1670
+ {{ props.description }}
1671
+ </small>
1672
+
1673
+ <small *ngIf="showError" class="p-error block">
1674
+ <i class="pi pi-exclamation-circle mr-2"></i>
1675
+ {{ errorMessage }}
1676
+ </small>
1677
+ </div>
1678
+ `, styles: [":is() .required-star{color:var(--red-500);margin-left:.25rem}:is() .p-input-error :where(input,.p-inputtext){border-color:var(--red-500)}:is() .p-input-error :where(input:enabled:focus,.p-inputtext:enabled:focus){border-color:var(--red-500);box-shadow:0 0 0 1px var(--red-500)}:is() .p-error{color:var(--red-500);margin-top:.5rem}:is() small{color:var(--text-color-secondary)}\n"] }]
1679
+ }] });
1680
+
1681
+ const FORMLY_TYPES = {
1682
+ TEXT: 'text',
1683
+ NUMBER: 'number',
1684
+ DATE: 'date',
1685
+ CHECKBOX: 'checkbox',
1686
+ RADIO: 'radio',
1687
+ SELECT: 'select',
1688
+ MULTISELECT: 'multiselect',
1689
+ TEXTAREA: 'textarea'
1690
+ };
1691
+ const FORMLY_WRAPPERS = {
1692
+ FORM_FIELD: 'form-field'
1693
+ };
1694
+
1695
+ class CoreFormlyModule {
1696
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CoreFormlyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1697
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: CoreFormlyModule, imports: [i2$1.FormlyModule], exports: [FormlyModule] });
1698
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CoreFormlyModule, imports: [FormlyModule.forRoot({
1699
+ types: [
1700
+ { name: FORMLY_TYPES.TEXT, component: TextInputType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1701
+ { name: FORMLY_TYPES.NUMBER, component: NumericInputType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1702
+ { name: FORMLY_TYPES.DATE, component: DateType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1703
+ { name: FORMLY_TYPES.CHECKBOX, component: CheckboxType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1704
+ { name: FORMLY_TYPES.RADIO, component: RadioType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1705
+ { name: FORMLY_TYPES.SELECT, component: SelectType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1706
+ { name: FORMLY_TYPES.MULTISELECT, component: MultiselectType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1707
+ { name: FORMLY_TYPES.TEXTAREA, component: TextareaType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] }, // Added textarea type
1708
+ ],
1709
+ wrappers: [
1710
+ { name: FORMLY_WRAPPERS.FORM_FIELD, component: FormFieldWrapper }
1711
+ ]
1712
+ }), FormlyModule] });
1713
+ }
1714
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CoreFormlyModule, decorators: [{
1715
+ type: NgModule,
1716
+ args: [{
1717
+ imports: [
1718
+ FormlyModule.forRoot({
1719
+ types: [
1720
+ { name: FORMLY_TYPES.TEXT, component: TextInputType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1721
+ { name: FORMLY_TYPES.NUMBER, component: NumericInputType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1722
+ { name: FORMLY_TYPES.DATE, component: DateType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1723
+ { name: FORMLY_TYPES.CHECKBOX, component: CheckboxType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1724
+ { name: FORMLY_TYPES.RADIO, component: RadioType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1725
+ { name: FORMLY_TYPES.SELECT, component: SelectType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1726
+ { name: FORMLY_TYPES.MULTISELECT, component: MultiselectType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
1727
+ { name: FORMLY_TYPES.TEXTAREA, component: TextareaType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] }, // Added textarea type
1728
+ ],
1729
+ wrappers: [
1730
+ { name: FORMLY_WRAPPERS.FORM_FIELD, component: FormFieldWrapper }
1731
+ ]
1732
+ })
1733
+ ],
1734
+ exports: [FormlyModule]
1735
+ }]
1736
+ }] });
1737
+
1738
+ /**
1739
+ * Base component for all page components in the application.
1740
+ * Provides common functionality like loading state and message display.
1741
+ */
1742
+ class BasePageComponent {
1743
+ utilsService;
1744
+ notificationService;
1745
+ loading = false;
1746
+ constructor(utilsService, notificationService) {
1747
+ this.utilsService = utilsService;
1748
+ this.notificationService = notificationService;
1749
+ }
1750
+ /**
1751
+ * Display an error message to the user
1752
+ * @param detail The error message to display
1753
+ */
1754
+ showError(detail) {
1755
+ this.notificationService.showError(detail);
1756
+ }
1757
+ /**
1758
+ * Display a success message to the user
1759
+ * @param detail The success message to display
1760
+ */
1761
+ showSuccess(detail) {
1762
+ this.notificationService.showSuccess(detail);
1763
+ }
1764
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BasePageComponent, deps: [{ token: UtilsService }, { token: NotificationService }], target: i0.ɵɵFactoryTarget.Directive });
1765
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: BasePageComponent, isStandalone: true, ngImport: i0 });
1766
+ }
1767
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BasePageComponent, decorators: [{
1768
+ type: Directive
1769
+ }], ctorParameters: () => [{ type: UtilsService }, { type: NotificationService }] });
1770
+
1771
+ class BaseFormComponent extends BasePageComponent {
1772
+ route;
1773
+ router;
1774
+ notificationService;
1775
+ utilsService;
1776
+ formlyConfigService;
1777
+ destroyed$ = new Subject();
1778
+ state = {
1779
+ form: new FormGroup({}),
1780
+ fields: [],
1781
+ isNew: true,
1782
+ mode: AppConstants.PAGE_MODE.CREATE,
1783
+ currentData: {},
1784
+ originalData: null,
1785
+ hasChanges: false,
1786
+ loading: false,
1787
+ context: ComponentContext.STANDALONE
1788
+ };
1789
+ constructor(route, router, notificationService, utilsService, formlyConfigService) {
1790
+ super(utilsService, notificationService);
1791
+ this.route = route;
1792
+ this.router = router;
1793
+ this.notificationService = notificationService;
1794
+ this.utilsService = utilsService;
1795
+ this.formlyConfigService = formlyConfigService;
1796
+ }
1797
+ ngOnInit() {
1798
+ this.initializeForm()
1799
+ .pipe(switchMap(() => this.loadFormData()), takeUntil(this.destroyed$))
1800
+ .subscribe({
1801
+ next: () => this.onFormReady(),
1802
+ error: error => this.handleInitError(error)
1803
+ });
1804
+ }
1805
+ initializeForm() {
1806
+ return this.route.queryParams.pipe(take(1), map(params => {
1807
+ this.initializeFormMode(params[AppConstants.FORM.MODE]);
1808
+ this.setupFormState();
1809
+ this.initializeFormSubscriptions();
1810
+ }));
1811
+ }
1812
+ loadFormData() {
1813
+ return this.route.queryParams.pipe(take(1), mergeMap(params => {
1814
+ const state = this.getRouterState();
1815
+ if (this.state.mode === AppConstants.PAGE_MODE.CREATE) {
1816
+ return of(void 0);
1817
+ }
1818
+ if (state?.formData) {
1819
+ this.updateFormState(state.formData);
1820
+ return of(void 0);
1821
+ }
1822
+ const uuid = params[AppConstants.FORM.UUID];
1823
+ if (!uuid) {
1824
+ throw new Error(`UUID is required for ${this.state.mode} mode`);
1825
+ }
1826
+ return this.getEntityOperations().getById(uuid)
1827
+ .pipe(tap(data => this.updateFormState(data)), map(() => void 0));
1828
+ }));
1829
+ }
1830
+ getRouterState() {
1831
+ return {
1832
+ ...this.router.getCurrentNavigation()?.extras?.state,
1833
+ ...window.history.state,
1834
+ ...this.route.snapshot.data
1835
+ };
1836
+ }
1837
+ initializeFormSubscriptions() {
1838
+ this.state.form.valueChanges
1839
+ .pipe(debounceTime(300), distinctUntilChanged(), takeUntil(this.destroyed$))
1840
+ .subscribe(() => {
1841
+ this.updateCurrentData();
1842
+ this.checkFormChanges();
1843
+ });
1844
+ // Check for context in router state
1845
+ const routerState = window.history.state;
1846
+ if (routerState?.context) {
1847
+ this.state.context = routerState.context;
1848
+ console.log('[BaseFormComponent] Context set from router state:', this.state.context);
1849
+ }
1850
+ }
1851
+ set formData(data) {
1852
+ console.log('[BaseFormComponent] Form data input changed:', data);
1853
+ if (data) {
1854
+ // Reset form with new data
1855
+ this.state.fields = this.getFormlyFieldConfig();
1856
+ this.state.form.reset();
1857
+ this.state.currentData = { ...data };
1858
+ this.state.originalData = data;
1859
+ this.state.isNew = false;
1860
+ // Update form values after reset
1861
+ this.state.form.patchValue(data);
1862
+ console.log('[BaseFormComponent] Form state updated:', {
1863
+ fields: this.state.fields,
1864
+ formValue: this.state.form.value,
1865
+ currentData: this.state.currentData
1866
+ });
1867
+ }
1868
+ }
1869
+ updateCurrentData() {
1870
+ this.state.currentData = {
1871
+ ...this.state.form.value,
1872
+ uuid: this.state.originalData?.uuid
1873
+ };
1874
+ }
1875
+ checkFormChanges() {
1876
+ this.state.hasChanges = this.hasFormChanges(this.state.currentData);
1877
+ }
1878
+ updateFormState(data) {
1879
+ this.state = {
1880
+ ...this.state,
1881
+ currentData: { ...data },
1882
+ originalData: data,
1883
+ isNew: false,
1884
+ loading: false
1885
+ };
1886
+ this.state.form.patchValue(data, { emitEvent: false });
1887
+ }
1888
+ handleInitError(error) {
1889
+ console.error('Form initialization error:', error);
1890
+ this.notificationService.showError(AppMessages.FORM.ERROR.INIT);
1891
+ this.state.loading = false;
1892
+ }
1893
+ handleLoadError(error) {
1894
+ const entityName = this.getEntityName();
1895
+ console.error(`Error loading ${entityName}:`, error);
1896
+ this.notificationService.showError(`${AppMessages.FORM.ERROR.LOAD} ${entityName}`);
1897
+ this.state.loading = false;
1898
+ }
1899
+ patchFormData(data) {
1900
+ this.formData = data;
1901
+ }
1902
+ initializeFormMode(routeMode) {
1903
+ if (!routeMode) {
1904
+ this.state.mode = AppConstants.PAGE_MODE.CREATE;
1905
+ }
1906
+ else if (this.isValidPageMode(routeMode)) {
1907
+ this.state.mode = routeMode;
1908
+ }
1909
+ else {
1910
+ throw new Error(`Invalid page mode: ${routeMode}`);
1911
+ }
1912
+ }
1913
+ setupFormState() {
1914
+ this.state.fields = this.getFormlyFieldConfig();
1915
+ if (this.state.mode === AppConstants.PAGE_MODE.CREATE) {
1916
+ this.state.isNew = true;
1917
+ this.state.loading = false;
1918
+ this.state.originalData = null;
1919
+ }
1920
+ if (this.state.mode === AppConstants.PAGE_MODE.VIEW) {
1921
+ this.state.form.disable();
1922
+ }
1923
+ }
1924
+ onFormReady() {
1925
+ console.log('[BaseFormComponent] Form initialized successfully');
1926
+ }
1927
+ getFormlyFieldConfig() {
1928
+ return this.formlyConfigService.getFormConfig(this.getJsonFields(), this.state.mode, this.getDropdownOptions());
1929
+ }
1930
+ onSubmitRoute() {
1931
+ return [`/${this.getEntityName().toLowerCase()}s`];
1932
+ }
1933
+ getEntityOperations() {
1934
+ const entityService = this.getEntityService();
1935
+ return {
1936
+ create: (data) => entityService.create(data),
1937
+ update: (uuid, data) => entityService.update(uuid, { ...data, uuid }),
1938
+ getById: (uuid) => entityService.getById(uuid)
1939
+ };
1940
+ }
1941
+ onSubmit() {
1942
+ if (!this.validateSubmission()) {
1943
+ return;
1944
+ }
1945
+ this.state.loading = true;
1946
+ const operation$ = this.getSubmitOperation();
1947
+ operation$
1948
+ .pipe(finalize$1(() => this.state.loading = false), takeUntil(this.destroyed$))
1949
+ .subscribe({
1950
+ next: () => this.handleSubmitSuccess(),
1951
+ error: error => this.handleSubmitError(error)
1952
+ });
1953
+ }
1954
+ validateSubmission() {
1955
+ if (!this.state.form.valid) {
1956
+ this.notificationService.showError('Please check form errors');
1957
+ return false;
1958
+ }
1959
+ if (!this.state.hasChanges && !this.state.isNew) {
1960
+ this.notificationService.showSuccess('No changes to save');
1961
+ return false;
1962
+ }
1963
+ if (!this.state.currentData || Object.keys(this.state.currentData).length === 0) {
1964
+ this.notificationService.showError('No data to save');
1965
+ return false;
1966
+ }
1967
+ return true;
1968
+ }
1969
+ getSubmitOperation() {
1970
+ const formData = this.state.currentData;
1971
+ const operations = this.getEntityOperations();
1972
+ return this.state.isNew
1973
+ ? operations.create(formData)
1974
+ : operations.update(this.state.originalData.uuid, formData);
1975
+ }
1976
+ handleSubmitSuccess() {
1977
+ const entityName = this.getEntityName();
1978
+ this.notificationService.showSuccess(`${entityName} ${AppMessages.FORM.SUCCESS.SAVE}`);
1979
+ this.router.navigate(this.onSubmitRoute());
1980
+ }
1981
+ handleSubmitError(error) {
1982
+ const entityName = this.getEntityName();
1983
+ const errorMessage = error.error?.message || `${AppMessages.FORM.ERROR.SAVE} ${entityName}`;
1984
+ this.showError(errorMessage);
1985
+ }
1986
+ hasFormChanges(currentData) {
1987
+ if (!this.state.originalData || this.state.isNew) {
1988
+ return true;
1989
+ }
1990
+ console.log('[BaseFormComponent] Checking for changes:', {
1991
+ currentData,
1992
+ originalData: this.state.originalData
1993
+ });
1994
+ const hasChanges = Object.keys(currentData).some(key => {
1995
+ const currentValue = currentData[key];
1996
+ const originalValue = this.state.originalData[key];
1997
+ const changed = currentValue !== originalValue;
1998
+ if (changed) {
1999
+ console.log(`[BaseFormComponent] Field '${key}' changed:`, {
2000
+ from: originalValue,
2001
+ to: currentValue
2002
+ });
2003
+ }
2004
+ return changed;
2005
+ });
2006
+ console.log('[BaseFormComponent] Changes detected:', hasChanges);
2007
+ return hasChanges;
2008
+ }
2009
+ ngOnDestroy() {
2010
+ this.destroyed$.next();
2011
+ this.destroyed$.complete();
2012
+ }
2013
+ isValidPageMode(mode) {
2014
+ return Object.values(AppConstants.PAGE_MODE).includes(mode);
2015
+ }
2016
+ getDropdownOptions() {
2017
+ return {};
2018
+ }
2019
+ /**
2020
+ * Checks if the component is running in a specific context
2021
+ * @param contextType The context type to check against
2022
+ */
2023
+ isInContext(contextType) {
2024
+ return this.state.context === contextType;
2025
+ }
2026
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseFormComponent, deps: [{ token: i1$1.ActivatedRoute }, { token: i1$1.Router }, { token: NotificationService }, { token: UtilsService }, { token: FormlyConfigService }], target: i0.ɵɵFactoryTarget.Component });
2027
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: BaseFormComponent, isStandalone: true, selector: "ng-component", inputs: { formData: "formData" }, usesInheritance: true, ngImport: i0, template: '', isInline: true });
2028
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseFormComponent });
2029
+ }
2030
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseFormComponent, decorators: [{
2031
+ type: Injectable
2032
+ }, {
2033
+ type: Component,
2034
+ args: [{
2035
+ template: ''
2036
+ }]
2037
+ }], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: NotificationService }, { type: UtilsService }, { type: FormlyConfigService }], propDecorators: { formData: [{
2038
+ type: Input
2039
+ }] } });
2040
+
2041
+ /**
2042
+ * Base component for all table components in the application.
2043
+ * Provides common functionality for table operations like loading data,
2044
+ * pagination, sorting, and filtering.
2045
+ */
2046
+ class BaseTableComponent extends BasePageComponent {
2047
+ utilsService;
2048
+ notificationService;
2049
+ table;
2050
+ items = [];
1139
2051
  rows = 10;
1140
2052
  totalRecords = 0;
1141
2053
  filters = [];
1142
2054
  subscriptions = [];
1143
- constructor(router, notificationService, utilsService, confirmationService) {
2055
+ constructor(utilsService, notificationService) {
1144
2056
  super(utilsService, notificationService);
1145
- this.router = router;
1146
- this.notificationService = notificationService;
1147
2057
  this.utilsService = utilsService;
1148
- this.confirmationService = confirmationService;
2058
+ this.notificationService = notificationService;
1149
2059
  }
1150
2060
  /**
1151
- * Initialize the component, subscribe to loading state if available,
1152
- * and load initial data.
2061
+ * Initialize the component and load initial data
1153
2062
  */
1154
2063
  ngOnInit() {
1155
2064
  // Subscribe to loading state if available
@@ -1179,16 +2088,51 @@ class BaseListPageComponent extends BasePageComponent {
1179
2088
  * Load data with pagination and sorting
1180
2089
  * @param event The lazy load event from PrimeNG table
1181
2090
  */
1182
- /**
1183
- * Convert PrimeNG filter format to backend format
1184
- * @param filters The PrimeNG filters object
1185
- * @returns Converted filters object for backend
1186
- */
1187
- /**
1188
- * Convert PrimeNG filter format to backend format
1189
- * @param filters The PrimeNG filters object
1190
- * @returns Converted filters object for backend
1191
- */
2091
+ loadData(event) {
2092
+ console.log('LoadData called with event:', event);
2093
+ this.loading = true;
2094
+ // Create page request with default values
2095
+ const pageRequest = {
2096
+ page: 0,
2097
+ size: 10,
2098
+ sorts: [],
2099
+ filters: []
2100
+ };
2101
+ if (event) {
2102
+ // Handle pagination
2103
+ if (event.first !== undefined && event.rows) {
2104
+ pageRequest.page = Math.floor(event.first / event.rows);
2105
+ pageRequest.size = event.rows;
2106
+ }
2107
+ // Handle sorting
2108
+ if (event.sortField) {
2109
+ pageRequest.sorts = [{
2110
+ field: Array.isArray(event.sortField) ? event.sortField[0] : event.sortField,
2111
+ direction: event.sortOrder === 1 ? 'asc' : 'desc'
2112
+ }];
2113
+ }
2114
+ // Handle filtering
2115
+ if (event.filters) {
2116
+ const convertedFilters = this.convertFilters(event.filters);
2117
+ console.log('Converted filters:', convertedFilters);
2118
+ pageRequest.filters = convertedFilters;
2119
+ }
2120
+ }
2121
+ console.log('Sending page request:', pageRequest);
2122
+ this.getEntityService().getAll(pageRequest)
2123
+ .pipe(finalize$1(() => this.loading = false))
2124
+ .subscribe({
2125
+ next: (response) => {
2126
+ this.items = [...response.content];
2127
+ this.totalRecords = response.totalElements;
2128
+ this.rows = response.pageable.pageSize;
2129
+ },
2130
+ error: (error) => {
2131
+ console.error('Failed to load data:', error);
2132
+ this.showError(`Failed to load data`);
2133
+ }
2134
+ });
2135
+ }
1192
2136
  /**
1193
2137
  * Convert PrimeNG filters to backend format with proper operator mapping
1194
2138
  */
@@ -1260,136 +2204,6 @@ class BaseListPageComponent extends BasePageComponent {
1260
2204
  };
1261
2205
  return operatorMap[operator] || 'EQUALS'; // Default to EQUALS if operator not found
1262
2206
  }
1263
- loadData(event) {
1264
- console.log('LoadData called with event:', event);
1265
- this.loading = true;
1266
- // Create page request with default values
1267
- const pageRequest = {
1268
- page: 0,
1269
- size: 10,
1270
- sorts: [],
1271
- filters: []
1272
- };
1273
- if (event) {
1274
- // Handle pagination
1275
- if (event.first !== undefined && event.rows) {
1276
- pageRequest.page = Math.floor(event.first / event.rows);
1277
- pageRequest.size = event.rows;
1278
- }
1279
- // Handle sorting
1280
- if (event.sortField) {
1281
- pageRequest.sorts = [{
1282
- field: Array.isArray(event.sortField) ? event.sortField[0] : event.sortField,
1283
- direction: event.sortOrder === 1 ? 'asc' : 'desc'
1284
- }];
1285
- }
1286
- // Handle filtering
1287
- if (event.filters) {
1288
- const convertedFilters = this.convertFilters(event.filters);
1289
- console.log('Converted filters:', convertedFilters);
1290
- pageRequest.filters = convertedFilters;
1291
- }
1292
- }
1293
- console.log('Sending page request:', pageRequest);
1294
- this.getEntityService().getAll(pageRequest)
1295
- .pipe(finalize$1(() => this.loading = false))
1296
- .subscribe({
1297
- next: (response) => {
1298
- this.items = [...response.content];
1299
- this.totalRecords = response.page.totalElements;
1300
- },
1301
- error: (error) => {
1302
- console.error('Failed to load data:', error);
1303
- this.showError(`${AppMessages.LIST.ERROR.LOAD} ${this.getEntityName()}`);
1304
- }
1305
- });
1306
- }
1307
- /**
1308
- * Navigate to create a new entity
1309
- */
1310
- onCreate() {
1311
- // Navigate to the form route and pass the mode as a query parameter
1312
- this.navigateTo([this.getFormRoute()], { [AppConstants.FORM.MODE]: AppConstants.PAGE_MODE.CREATE });
1313
- }
1314
- /**
1315
- * Navigate to view an entity
1316
- * @param item The entity to view
1317
- */
1318
- onView(item) {
1319
- if (!this.validateItemUuid(item, 'view'))
1320
- return;
1321
- // Correct: Navigate to form route, pass mode and uuid as query params
1322
- this.navigateTo([this.getFormRoute()], {
1323
- [AppConstants.FORM.MODE]: AppConstants.PAGE_MODE.VIEW,
1324
- [AppConstants.FORM.UUID]: item.uuid
1325
- });
1326
- }
1327
- /**
1328
- * Navigate to edit an entity
1329
- * @param item The entity to edit
1330
- */
1331
- onEdit(item) {
1332
- if (!this.validateItemUuid(item, 'edit'))
1333
- return;
1334
- // Correct: Navigate to form route, pass mode and uuid as query params
1335
- this.navigateTo([this.getFormRoute()], {
1336
- [AppConstants.FORM.MODE]: AppConstants.PAGE_MODE.EDIT,
1337
- [AppConstants.FORM.UUID]: item.uuid
1338
- });
1339
- }
1340
- /**
1341
- * Navigate to a route with error handling
1342
- * @param path The route path
1343
- * @param matrixParams Optional matrix parameters
1344
- */
1345
- navigateTo(path, matrixParams) {
1346
- this.router.navigate(path, { queryParams: matrixParams })
1347
- .catch(error => {
1348
- console.error('Navigation error:', error);
1349
- this.showError(AppMessages.LIST.ERROR.NAVIGATION);
1350
- });
1351
- }
1352
- /**
1353
- * Delete an entity with confirmation
1354
- * @param item The entity to delete
1355
- */
1356
- onDelete(item) {
1357
- if (!this.validateItemUuid(item, 'delete'))
1358
- return;
1359
- this.confirmationService.confirm({
1360
- message: `Are you sure you want to delete this ${this.getEntityName()}?`,
1361
- header: 'Confirm Deletion',
1362
- icon: 'pi pi-exclamation-triangle',
1363
- accept: () => {
1364
- this.actionLoading = true;
1365
- this.getEntityService().delete(item.uuid)
1366
- .pipe(finalize$1(() => this.actionLoading = false))
1367
- .subscribe({
1368
- next: () => {
1369
- this.showSuccess(`${this.getEntityName()} ${AppMessages.LIST.SUCCESS.DELETE}`);
1370
- this.loadData();
1371
- },
1372
- error: (error) => {
1373
- console.error('Delete failed:', error);
1374
- this.showError(`${AppMessages.LIST.ERROR.DELETE} ${this.getEntityName()}`);
1375
- }
1376
- });
1377
- }
1378
- });
1379
- }
1380
- /**
1381
- * Validate that an entity has a UUID
1382
- * @param item The entity to validate
1383
- * @param action The action being performed
1384
- * @returns true if valid, false otherwise
1385
- */
1386
- validateItemUuid(item, action) {
1387
- if (!item?.uuid) {
1388
- this.showError(`${AppMessages.LIST.ERROR.INVALID_ID} ${action}`);
1389
- return false;
1390
- }
1391
- return true;
1392
- }
1393
2207
  /**
1394
2208
  * Get an observable for loading state from the entity service
1395
2209
  * @returns An observable of loading state if available, null otherwise
@@ -1398,1002 +2212,782 @@ class BaseListPageComponent extends BasePageComponent {
1398
2212
  const service = this.getEntityService();
1399
2213
  return service.loading$ || null;
1400
2214
  }
1401
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseListPageComponent, deps: [{ token: i1$2.Router }, { token: NotificationService }, { token: UtilsService }, { token: i1.ConfirmationService }], target: i0.ɵɵFactoryTarget.Directive });
1402
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: BaseListPageComponent, isStandalone: true, viewQueries: [{ propertyName: "table", first: true, predicate: ["dt"], descendants: true }], usesInheritance: true, ngImport: i0 });
2215
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTableComponent, deps: [{ token: UtilsService }, { token: NotificationService }], target: i0.ɵɵFactoryTarget.Directive });
2216
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: BaseTableComponent, isStandalone: true, viewQueries: [{ propertyName: "table", first: true, predicate: ["dt"], descendants: true }], usesInheritance: true, ngImport: i0 });
1403
2217
  }
1404
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseListPageComponent, decorators: [{
2218
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTableComponent, decorators: [{
1405
2219
  type: Directive
1406
- }], ctorParameters: () => [{ type: i1$2.Router }, { type: NotificationService }, { type: UtilsService }, { type: i1.ConfirmationService }], propDecorators: { table: [{
2220
+ }], ctorParameters: () => [{ type: UtilsService }, { type: NotificationService }], propDecorators: { table: [{
1407
2221
  type: ViewChild,
1408
2222
  args: ['dt']
1409
2223
  }] } });
1410
2224
 
1411
- class BaseTabbedFormComponent extends BasePageComponent {
1412
- route;
2225
+ /**
2226
+ * Base component for all list page components in the application.
2227
+ * Provides common functionality for list operations like CRUD operations
2228
+ * while inheriting table functionality from BaseTableComponent.
2229
+ */
2230
+ class BaseListPageComponent extends BaseTableComponent {
1413
2231
  router;
1414
2232
  notificationService;
1415
2233
  utilsService;
1416
- // Tab specific properties
1417
- activeTab = '';
1418
- tabs = [];
1419
- formType = '';
1420
- // Form properties
1421
- isNew = true;
1422
- mode = AppConstants.PAGE_MODE.CREATE;
1423
- currentFormData;
1424
- originalFormData = null;
1425
- hasChanges = false;
1426
- // debug method removed
1427
- constructor(route, router, notificationService, utilsService) {
2234
+ confirmationService;
2235
+ actionLoading = false;
2236
+ constructor(router, notificationService, utilsService, confirmationService) {
1428
2237
  super(utilsService, notificationService);
1429
- this.route = route;
1430
2238
  this.router = router;
1431
2239
  this.notificationService = notificationService;
1432
2240
  this.utilsService = utilsService;
2241
+ this.confirmationService = confirmationService;
1433
2242
  }
1434
- ngOnInit() {
1435
- try {
1436
- // Initialize tabs
1437
- this.tabs = this.getTabs();
1438
- this.activeTab = this.tabs[0]?.value || '';
1439
- // Initialize form using query params
1440
- this.route.queryParams.pipe(take(1)).subscribe({
1441
- next: params => this.initializeForm(params),
1442
- error: error => {
1443
- console.error('Error initializing form:', error);
1444
- this.showError(AppMessages.FORM.ERROR.INIT);
1445
- }
1446
- });
1447
- }
1448
- catch (error) {
1449
- console.error('Error in ngOnInit:', error);
1450
- this.showError(AppMessages.FORM.ERROR.INIT);
1451
- }
1452
- }
1453
- initializeFormData() {
1454
- console.log('[BaseTabbedFormComponent] Initializing form data');
1455
- console.log('[BaseTabbedFormComponent] Current mode:', this.mode);
1456
- if (this.mode === AppConstants.PAGE_MODE.CREATE) {
1457
- console.log('[BaseTabbedFormComponent] Create mode, using defaults');
1458
- this.loadDefaultTab();
1459
- return;
1460
- }
1461
- const state = {
1462
- ...this.router.getCurrentNavigation()?.extras?.state,
1463
- ...window.history.state
1464
- };
1465
- console.log('[BaseTabbedFormComponent] Combined state:', state);
1466
- if (state?.formData) {
1467
- console.log('[BaseTabbedFormComponent] Found form data in state:', state.formData);
1468
- this.patchFormData(state.formData);
1469
- this.loadDefaultTab();
1470
- }
1471
- else {
1472
- console.log('[BaseTabbedFormComponent] No form data, loading from UUID');
1473
- this.loadFromUuid();
1474
- }
1475
- }
1476
- loadFromUuid() {
1477
- // Flatten nested subscriptions using RxJS operators
1478
- this.route.queryParams.pipe(take(1), map((params) => {
1479
- const uuid = params[AppConstants.FORM.UUID];
1480
- if (!uuid) {
1481
- throw new Error(`UUID is required for ${this.mode} mode when no formData is provided`);
1482
- }
1483
- return uuid;
1484
- })).subscribe({
1485
- next: (uuid) => this.loadEntity(uuid),
1486
- error: (error) => {
1487
- console.error('Error loading UUID:', error);
1488
- this.showError(`${AppMessages.FORM.ERROR.LOAD} UUID`);
1489
- }
1490
- });
1491
- }
1492
- initializeForm(params) {
1493
- const routeMode = params[AppConstants.FORM.MODE];
1494
- try {
1495
- this.initializeFormMode(routeMode);
1496
- this.setupFormState();
1497
- if (this.formType === TabbedFormType.LIST) {
1498
- this.initializeListForm(params);
1499
- }
1500
- else {
1501
- // Existing logic for STANDARD form type
1502
- this.initializeFormData();
1503
- }
1504
- }
1505
- catch (error) {
1506
- console.error('Error initializing form:', error);
1507
- this.showError(`${AppMessages.FORM.ERROR.INIT}: ${error instanceof Error ? error.message : 'Unknown error'}`);
1508
- }
1509
- }
1510
- patchFormData(data) {
1511
- if (data) {
1512
- this.currentFormData = { ...data };
1513
- this.originalFormData = { ...data }; // Create a deep copy to avoid reference issues
1514
- this.isNew = false;
1515
- }
1516
- }
1517
- initializeFormMode(routeMode) {
1518
- if (!routeMode) {
1519
- this.mode = AppConstants.PAGE_MODE.CREATE;
1520
- return;
1521
- }
1522
- // Validate mode more explicitly
1523
- if (!this.isValidPageMode(routeMode)) {
1524
- const validModes = Object.values(AppConstants.PAGE_MODE).join(', ');
1525
- throw new Error(`Invalid page mode: ${routeMode}. Valid modes are: ${validModes}`);
1526
- }
1527
- this.mode = routeMode;
1528
- }
1529
- setupFormState() {
1530
- console.log('[BaseTabbedFormComponent] Setting up form state, mode:', this.mode);
1531
- if (this.mode === AppConstants.PAGE_MODE.CREATE) {
1532
- console.log('[BaseTabbedFormComponent] Create mode setup');
1533
- this.isNew = true;
1534
- this.loading = false;
1535
- this.originalFormData = null;
1536
- }
1537
- }
1538
- loadEntity(uuid) {
1539
- this.loading = true;
1540
- this.isNew = false;
1541
- const entityName = this.getEntityName();
1542
- this.getEntityService().getById(uuid).subscribe({
1543
- next: (data) => {
1544
- this.originalFormData = data;
1545
- this.currentFormData = { ...data };
1546
- this.loading = false;
1547
- console.log('[BaseTabbedFormComponent] Original data loaded:', this.originalFormData);
1548
- console.log('[BaseTabbedFormComponent] Form data updated:', this.currentFormData);
1549
- this.loadDefaultTab();
1550
- },
1551
- error: (error) => {
1552
- console.log('[BaseTabbedFormComponent] Error loading entity:', error);
1553
- this.showError(`${AppMessages.FORM.ERROR.LOAD} ${entityName}`);
1554
- this.loading = false;
1555
- }
1556
- });
1557
- }
1558
- findTabByValue(value) {
1559
- return this.tabs.find(tab => tab.value === value);
1560
- }
1561
- loadDefaultTab() {
1562
- console.log('[BaseTabbedFormComponent] Loading default tab');
1563
- const defaultTab = this.tabs.find(tab => tab.isParentModel) || this.tabs[0];
1564
- if (!defaultTab)
1565
- return;
1566
- if (this.formType === TabbedFormType.LIST) {
1567
- const state = this.router.getCurrentNavigation()?.extras?.state || window.history.state;
1568
- const formDataList = state?.formDataList || [];
1569
- const selectedData = formDataList.find((data) => data.uuid === defaultTab.value);
1570
- if (selectedData) {
1571
- this.currentFormData = selectedData;
1572
- this.originalFormData = { ...selectedData };
1573
- this.patchFormData(selectedData);
1574
- }
1575
- this.router.navigate([defaultTab.value], {
1576
- relativeTo: this.route,
1577
- state: {
1578
- formData: selectedData,
1579
- formDataList: formDataList,
1580
- context: ComponentContext.TABBED
1581
- }
1582
- });
1583
- }
1584
- else if (this.formType === TabbedFormType.STANDARD) {
1585
- // For STANDARD type, just navigate to the tab route with the parent form data
1586
- this.router.navigate([defaultTab.value], {
1587
- relativeTo: this.route,
1588
- queryParamsHandling: 'preserve', // Keep existing query params (mode & uuid)
1589
- state: {
1590
- formData: defaultTab.isParentModel ? this.currentFormData : null,
1591
- parentFormData: this.currentFormData, // Pass the parent form data for child forms
1592
- context: ComponentContext.TABBED
1593
- }
1594
- });
1595
- }
1596
- else {
1597
- // Default behavior for other form types
1598
- this.router.navigate([defaultTab.value], {
1599
- relativeTo: this.route,
1600
- queryParamsHandling: 'preserve', // Keep existing query params (mode & uuid)
1601
- state: {
1602
- formData: this.currentFormData,
1603
- context: ComponentContext.TABBED
1604
- }
1605
- });
1606
- }
1607
- }
1608
- getTabFormData(tabConfig) {
1609
- if (!tabConfig)
1610
- return null;
1611
- if (tabConfig.isParentModel) {
1612
- return this.currentFormData;
1613
- }
1614
- const fieldName = this.utilsService.toCamelCase(tabConfig.value);
1615
- // Use type assertion since we know this is a valid field
1616
- return this.currentFormData[fieldName];
2243
+ /**
2244
+ * Navigate to create a new entity
2245
+ */
2246
+ onCreate() {
2247
+ // Navigate to the form route and pass the mode as a query parameter
2248
+ this.navigateTo([this.getFormRoute()], { [AppConstants.FORM.MODE]: AppConstants.PAGE_MODE.CREATE });
1617
2249
  }
1618
- initializeListForm(params) {
1619
- const state = this.router.getCurrentNavigation()?.extras?.state || window.history.state;
1620
- const formDataList = state?.formDataList;
1621
- if (!formDataList?.length) {
1622
- throw new Error('formDataList is required in state for LIST type');
1623
- }
1624
- this.tabs = this.createTabsFromFormDataList(formDataList);
1625
- // Select the first tab by default
1626
- this.activeTab = this.tabs[0].value;
1627
- // Set current form data from the first item in the list
1628
- this.currentFormData = formDataList[0];
1629
- this.originalFormData = formDataList[0];
1630
- // Navigate to the first tab
1631
- this.router.navigate([this.activeTab], {
1632
- relativeTo: this.route,
1633
- state: {
1634
- formData: this.currentFormData,
1635
- formDataList: formDataList,
1636
- context: ComponentContext.TABBED
1637
- }
2250
+ /**
2251
+ * Navigate to view an entity
2252
+ * @param item The entity to view
2253
+ */
2254
+ onView(item) {
2255
+ if (!this.validateItemUuid(item, 'view'))
2256
+ return;
2257
+ // Navigate to form route, pass mode and uuid as query params
2258
+ this.navigateTo([this.getFormRoute()], {
2259
+ [AppConstants.FORM.MODE]: AppConstants.PAGE_MODE.VIEW,
2260
+ [AppConstants.FORM.UUID]: item.uuid
1638
2261
  });
1639
- // Ensure the form data is updated after navigation
1640
- this.patchFormData(this.currentFormData);
1641
2262
  }
1642
- createTabsFromFormDataList(formDataList) {
1643
- return formDataList.map((data) => ({
1644
- value: data.uuid,
1645
- title: this.getTabTitle(data),
1646
- isParentModel: true
1647
- }));
2263
+ /**
2264
+ * Navigate to edit an entity
2265
+ * @param item The entity to edit
2266
+ */
2267
+ onEdit(item) {
2268
+ if (!this.validateItemUuid(item, 'edit'))
2269
+ return;
2270
+ // Navigate to form route, pass mode and uuid as query params
2271
+ this.navigateTo([this.getFormRoute()], {
2272
+ [AppConstants.FORM.MODE]: AppConstants.PAGE_MODE.EDIT,
2273
+ [AppConstants.FORM.UUID]: item.uuid
2274
+ });
1648
2275
  }
1649
- onTabChange(value) {
1650
- const tabConfig = this.findTabByValue(value);
1651
- if (!tabConfig) {
1652
- console.warn(`Tab not found: ${value}`);
2276
+ /**
2277
+ * Delete an entity with confirmation
2278
+ * @param item The entity to delete
2279
+ */
2280
+ onDelete(item) {
2281
+ if (!this.validateItemUuid(item, 'delete'))
1653
2282
  return;
1654
- }
1655
- this.activeTab = tabConfig.value;
1656
- if (this.formType === TabbedFormType.LIST) {
1657
- const state = this.router.getCurrentNavigation()?.extras?.state || window.history.state;
1658
- const formDataList = state?.formDataList || [];
1659
- const selectedData = formDataList.find((data) => data.uuid === value);
1660
- if (selectedData) {
1661
- // Update current form data
1662
- this.currentFormData = selectedData;
1663
- this.originalFormData = { ...selectedData };
1664
- this.patchFormData(selectedData);
1665
- this.router.navigate([value], {
1666
- relativeTo: this.route,
1667
- state: {
1668
- formData: selectedData,
1669
- formDataList: formDataList,
1670
- context: ComponentContext.TABBED
2283
+ this.confirmationService.confirm({
2284
+ message: `Are you sure you want to delete this ${this.getEntityName()}?`,
2285
+ header: 'Confirm Deletion',
2286
+ icon: 'pi pi-exclamation-triangle',
2287
+ accept: () => {
2288
+ this.actionLoading = true;
2289
+ this.getEntityService().delete(item.uuid)
2290
+ .pipe(finalize$1(() => this.actionLoading = false))
2291
+ .subscribe({
2292
+ next: () => {
2293
+ this.showSuccess(`${this.getEntityName()} ${AppMessages.LIST.SUCCESS.DELETE}`);
2294
+ this.loadData();
2295
+ },
2296
+ error: (error) => {
2297
+ console.error('Delete failed:', error);
2298
+ this.showError(`${AppMessages.LIST.ERROR.DELETE} ${this.getEntityName()}`);
1671
2299
  }
1672
2300
  });
1673
2301
  }
1674
- }
1675
- else if (this.formType === TabbedFormType.STANDARD) {
1676
- // For STANDARD type, navigate to the tab route with the parent form data
1677
- this.router.navigate([this.activeTab], {
1678
- relativeTo: this.route,
1679
- queryParamsHandling: 'preserve',
1680
- state: {
1681
- formData: tabConfig.isParentModel ? this.currentFormData : null,
1682
- parentFormData: this.currentFormData,
1683
- context: ComponentContext.TABBED
1684
- }
1685
- });
1686
- }
1687
- else {
1688
- // Default behavior for other form types
1689
- const formData = this.getTabFormData(tabConfig);
1690
- this.router.navigate([this.activeTab], {
1691
- relativeTo: this.route,
1692
- queryParamsHandling: 'preserve',
1693
- state: {
1694
- formData,
1695
- context: ComponentContext.TABBED
1696
- }
1697
- });
1698
- }
1699
- }
1700
- hasFormChanges(currentData) {
1701
- // For new forms or no original data, consider it changed
1702
- if (this.isNew || !this.originalFormData) {
1703
- return true;
1704
- }
1705
- // Deep comparison of objects
1706
- return !this.isEqual(currentData, this.originalFormData);
1707
- }
1708
- isEqual(obj1, obj2) {
1709
- if (obj1 === obj2)
1710
- return true;
1711
- if (typeof obj1 !== 'object' || obj1 === null ||
1712
- typeof obj2 !== 'object' || obj2 === null)
1713
- return false;
1714
- const keys1 = Object.keys(obj1);
1715
- const keys2 = Object.keys(obj2);
1716
- if (keys1.length !== keys2.length)
1717
- return false;
1718
- return keys1.every(key => Object.prototype.hasOwnProperty.call(obj2, key) &&
1719
- this.isEqual(obj1[key], obj2[key]));
1720
- }
1721
- isValidPageMode(mode) {
1722
- return Object.values(AppConstants.PAGE_MODE).includes(mode);
1723
- }
1724
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTabbedFormComponent, deps: [{ token: i1$2.ActivatedRoute }, { token: i1$2.Router }, { token: NotificationService }, { token: UtilsService }], target: i0.ɵɵFactoryTarget.Component });
1725
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: BaseTabbedFormComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
1726
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTabbedFormComponent });
1727
- }
1728
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTabbedFormComponent, decorators: [{
1729
- type: Injectable
1730
- }, {
1731
- type: Component,
1732
- args: [{
1733
- template: ''
1734
- }]
1735
- }], ctorParameters: () => [{ type: i1$2.ActivatedRoute }, { type: i1$2.Router }, { type: NotificationService }, { type: UtilsService }] });
1736
-
1737
- class IndianDatePipe {
1738
- datePipe;
1739
- constructor(datePipe) {
1740
- this.datePipe = datePipe;
1741
- }
1742
- transform(value, format = 'default') {
1743
- if (!value)
1744
- return null;
1745
- // First validate dd-MM-yyyy format
1746
- const dateRegex = /^(0[1-9]|[12][0-9]|3[01])-(0[1-9]|1[0-2])-\d{4}$/;
1747
- if (!dateRegex.test(value)) {
1748
- throw new Error(`Invalid date format: ${value}. Expected format: dd-MM-yyyy`);
1749
- }
1750
- // Validate real date
1751
- const [day, month, year] = value.split('-').map(Number);
1752
- const date = new Date(year, month - 1, day);
1753
- if (date.getFullYear() !== year ||
1754
- date.getMonth() !== month - 1 ||
1755
- date.getDate() !== day) {
1756
- throw new Error(`Invalid date: ${value}`);
1757
- }
1758
- // Apply requested format
1759
- switch (format) {
1760
- case 'short':
1761
- // Pad with leading zeros for consistent format
1762
- const paddedDay = day.toString().padStart(2, '0');
1763
- const paddedMonth = month.toString().padStart(2, '0');
1764
- return `${paddedDay}-${paddedMonth}-${year}`; // 10-03-2025
1765
- case 'medium':
1766
- return this.datePipe.transform(date, 'dd MMM yyyy'); // 10 Jan 2019
1767
- default:
1768
- return value; // original dd-MM-yyyy
1769
- }
2302
+ });
1770
2303
  }
1771
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: IndianDatePipe, deps: [{ token: i1$3.DatePipe }], target: i0.ɵɵFactoryTarget.Pipe });
1772
- static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: IndianDatePipe, isStandalone: true, name: "indianDate" });
1773
- }
1774
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: IndianDatePipe, decorators: [{
1775
- type: Pipe,
1776
- args: [{
1777
- name: 'indianDate',
1778
- standalone: true
1779
- }]
1780
- }], ctorParameters: () => [{ type: i1$3.DatePipe }] });
1781
-
1782
- class TextInputType extends FieldType {
1783
- defaultOptions = {
1784
- props: {
1785
- type: 'text',
1786
- placeholder: ''
1787
- }
1788
- };
1789
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextInputType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1790
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: TextInputType, isStandalone: true, selector: "formly-field-text-input", usesInheritance: true, ngImport: i0, template: `
1791
- <input pInputText
1792
- [type]="props['type'] || 'text'"
1793
- [formControl]="$any(formControl)"
1794
- [formlyAttributes]="field"
1795
- [placeholder]="props['placeholder'] || ''"
1796
- [attr.aria-label]="props['label'] || props['placeholder'] || ''"
1797
- class="w-full"/>
1798
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i3.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] });
1799
- }
1800
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextInputType, decorators: [{
1801
- type: Component,
1802
- args: [{
1803
- selector: 'formly-field-text-input',
1804
- standalone: true,
1805
- imports: [
1806
- CommonModule,
1807
- ReactiveFormsModule,
1808
- FormlyModule,
1809
- InputTextModule
1810
- ],
1811
- template: `
1812
- <input pInputText
1813
- [type]="props['type'] || 'text'"
1814
- [formControl]="$any(formControl)"
1815
- [formlyAttributes]="field"
1816
- [placeholder]="props['placeholder'] || ''"
1817
- [attr.aria-label]="props['label'] || props['placeholder'] || ''"
1818
- class="w-full"/>
1819
- `
1820
- }]
1821
- }] });
1822
-
1823
- class SelectType extends FieldType {
1824
- get options$() {
1825
- const options = this.props['options'];
1826
- return isObservable(options) ? options : of(options || []);
2304
+ /**
2305
+ * Navigate to a route with error handling
2306
+ * @param path The route path
2307
+ * @param matrixParams Optional matrix parameters
2308
+ */
2309
+ navigateTo(path, matrixParams) {
2310
+ this.router.navigate(path, { queryParams: matrixParams })
2311
+ .catch(error => {
2312
+ console.error('Navigation error:', error);
2313
+ this.showError(AppMessages.LIST.ERROR.NAVIGATION);
2314
+ });
1827
2315
  }
1828
- defaultOptions = {
1829
- props: {
1830
- options: [],
1831
- optionLabel: 'label',
1832
- optionValue: 'value',
1833
- placeholder: 'Select',
1834
- showClear: false,
1835
- filter: false,
1836
- virtualScroll: false
1837
- }
1838
- };
1839
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: SelectType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1840
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: SelectType, isStandalone: true, selector: "formly-field-select", usesInheritance: true, ngImport: i0, template: `
1841
- <ng-container *ngIf="options$ | async as options">
1842
- <p-select
1843
- [formControl]="$any(formControl)"
1844
- [formlyAttributes]="field"
1845
- [options]="options"
1846
- [virtualScroll]="false"
1847
- [showClear]="false"
1848
- [placeholder]="props['placeholder'] || 'Select'"
1849
- styleClass="w-full">
1850
- </p-select>
1851
- </ng-container>
1852
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$3.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i4.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "size", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] });
1853
- }
1854
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: SelectType, decorators: [{
1855
- type: Component,
1856
- args: [{
1857
- selector: 'formly-field-select',
1858
- standalone: true,
1859
- imports: [
1860
- CommonModule,
1861
- ReactiveFormsModule,
1862
- FormlyModule,
1863
- SelectModule
1864
- ],
1865
- template: `
1866
- <ng-container *ngIf="options$ | async as options">
1867
- <p-select
1868
- [formControl]="$any(formControl)"
1869
- [formlyAttributes]="field"
1870
- [options]="options"
1871
- [virtualScroll]="false"
1872
- [showClear]="false"
1873
- [placeholder]="props['placeholder'] || 'Select'"
1874
- styleClass="w-full">
1875
- </p-select>
1876
- </ng-container>
1877
- `
1878
- }]
1879
- }] });
1880
-
1881
- class RadioType extends FieldType {
1882
- get options$() {
1883
- const options = this.props['options'];
1884
- return isObservable(options) ? options : of(options || []);
2316
+ /**
2317
+ * Validate that an entity has a UUID
2318
+ * @param item The entity to validate
2319
+ * @param action The action being performed
2320
+ * @returns true if valid, false otherwise
2321
+ */
2322
+ validateItemUuid(item, action) {
2323
+ if (!item?.uuid) {
2324
+ this.showError(`${AppMessages.LIST.ERROR.INVALID_ID} ${action}`);
2325
+ return false;
2326
+ }
2327
+ return true;
1885
2328
  }
1886
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RadioType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1887
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: RadioType, isStandalone: true, selector: "formly-field-radio", usesInheritance: true, ngImport: i0, template: `
1888
- <div class="flex flex-column gap-1">
1889
- <ng-container *ngIf="options$ | async as options">
1890
- <div *ngFor="let option of options; let i = index" class="flex align-items-center gap-2">
1891
- <p-radioButton
1892
- [name]="field.name || ''"
1893
- [value]="option.value"
1894
- [formControl]="$any(formControl)"
1895
- [formlyAttributes]="field"
1896
- [size]="'small'"
1897
- [inputId]="field.key + '_' + i">
1898
- </p-radioButton>
1899
- <label [for]="field.key + '_' + i" class="p-radio-label cursor-pointer">
1900
- {{option.label}}
1901
- </label>
1902
- </div>
1903
- </ng-container>
1904
- </div>
1905
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$3.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i4$1.RadioButton, selector: "p-radioButton, p-radiobutton, p-radio-button", inputs: ["value", "formControlName", "name", "disabled", "variant", "size", "tabindex", "inputId", "ariaLabelledBy", "ariaLabel", "style", "styleClass", "autofocus", "binary"], outputs: ["onClick", "onFocus", "onBlur"] }] });
2329
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseListPageComponent, deps: [{ token: i1$1.Router }, { token: NotificationService }, { token: UtilsService }, { token: i2.ConfirmationService }], target: i0.ɵɵFactoryTarget.Directive });
2330
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: BaseListPageComponent, isStandalone: true, usesInheritance: true, ngImport: i0 });
1906
2331
  }
1907
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: RadioType, decorators: [{
1908
- type: Component,
1909
- args: [{
1910
- selector: 'formly-field-radio',
1911
- standalone: true,
1912
- imports: [
1913
- CommonModule,
1914
- ReactiveFormsModule,
1915
- FormlyModule,
1916
- RadioButtonModule
1917
- ],
1918
- template: `
1919
- <div class="flex flex-column gap-1">
1920
- <ng-container *ngIf="options$ | async as options">
1921
- <div *ngFor="let option of options; let i = index" class="flex align-items-center gap-2">
1922
- <p-radioButton
1923
- [name]="field.name || ''"
1924
- [value]="option.value"
1925
- [formControl]="$any(formControl)"
1926
- [formlyAttributes]="field"
1927
- [size]="'small'"
1928
- [inputId]="field.key + '_' + i">
1929
- </p-radioButton>
1930
- <label [for]="field.key + '_' + i" class="p-radio-label cursor-pointer">
1931
- {{option.label}}
1932
- </label>
1933
- </div>
1934
- </ng-container>
1935
- </div>
1936
- `
1937
- }]
1938
- }] });
2332
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseListPageComponent, decorators: [{
2333
+ type: Directive
2334
+ }], ctorParameters: () => [{ type: i1$1.Router }, { type: NotificationService }, { type: UtilsService }, { type: i2.ConfirmationService }] });
1939
2335
 
1940
- class NumericInputType extends FieldType {
1941
- defaultOptions = {
1942
- props: {
1943
- min: undefined,
1944
- max: undefined,
1945
- minFractionDigits: 0,
1946
- maxFractionDigits: 0,
1947
- prefix: '',
1948
- suffix: '',
1949
- useGrouping: true
2336
+ class BaseTabbedFormComponent extends BasePageComponent {
2337
+ route;
2338
+ router;
2339
+ notificationService;
2340
+ utilsService;
2341
+ destroyed$ = new Subject();
2342
+ stateManager$ = new BehaviorSubject(this.getInitialState());
2343
+ getInitialState() {
2344
+ return {
2345
+ isNew: true,
2346
+ mode: AppConstants.PAGE_MODE.CREATE,
2347
+ currentData: {},
2348
+ originalData: null,
2349
+ hasChanges: false,
2350
+ loading: false,
2351
+ context: ComponentContext.TABBED,
2352
+ activeTab: '',
2353
+ tabs: []
2354
+ };
2355
+ }
2356
+ get state() {
2357
+ return this.stateManager$.value;
2358
+ }
2359
+ set state(value) {
2360
+ // Only emit if there are actual changes
2361
+ if (this.hasStateChanged(this.state, value)) {
2362
+ this.stateManager$.next(value);
1950
2363
  }
1951
- };
1952
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NumericInputType, deps: null, target: i0.ɵɵFactoryTarget.Component });
1953
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: NumericInputType, isStandalone: true, selector: "formly-field-numeric-input", usesInheritance: true, ngImport: i0, template: `
1954
- <p-inputNumber
1955
- [formControl]="$any(formControl)"
1956
- [formlyAttributes]="field"
1957
- [placeholder]="props['placeholder'] || ''"
1958
- [min]="props['min'] || 0"
1959
- [max]="props['max']"
1960
- [minFractionDigits]="props['minFractionDigits'] || 0"
1961
- [maxFractionDigits]="props['maxFractionDigits'] || 0"
1962
- [prefix]="props['prefix'] || ''"
1963
- [suffix]="props['suffix'] || ''"
1964
- [useGrouping]="props['useGrouping'] ?? true"
1965
- [showButtons]="true"
1966
- class="w-full">
1967
- </p-inputNumber>
1968
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: InputNumberModule }, { kind: "component", type: i3$1.InputNumber, selector: "p-inputNumber, p-inputnumber, p-input-number", inputs: ["showButtons", "format", "buttonLayout", "inputId", "styleClass", "style", "placeholder", "size", "maxlength", "tabindex", "title", "ariaLabelledBy", "ariaLabel", "ariaRequired", "name", "required", "autocomplete", "min", "max", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "readonly", "step", "allowEmpty", "locale", "localeMatcher", "mode", "currency", "currencyDisplay", "useGrouping", "variant", "minFractionDigits", "maxFractionDigits", "prefix", "suffix", "inputStyle", "inputStyleClass", "showClear", "autofocus", "disabled", "fluid"], outputs: ["onInput", "onFocus", "onBlur", "onKeyDown", "onClear"] }] });
1969
- }
1970
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: NumericInputType, decorators: [{
1971
- type: Component,
1972
- args: [{
1973
- selector: 'formly-field-numeric-input',
1974
- standalone: true,
1975
- imports: [
1976
- CommonModule,
1977
- ReactiveFormsModule,
1978
- FormlyModule,
1979
- InputNumberModule
1980
- ],
1981
- template: `
1982
- <p-inputNumber
1983
- [formControl]="$any(formControl)"
1984
- [formlyAttributes]="field"
1985
- [placeholder]="props['placeholder'] || ''"
1986
- [min]="props['min'] || 0"
1987
- [max]="props['max']"
1988
- [minFractionDigits]="props['minFractionDigits'] || 0"
1989
- [maxFractionDigits]="props['maxFractionDigits'] || 0"
1990
- [prefix]="props['prefix'] || ''"
1991
- [suffix]="props['suffix'] || ''"
1992
- [useGrouping]="props['useGrouping'] ?? true"
1993
- [showButtons]="true"
1994
- class="w-full">
1995
- </p-inputNumber>
1996
- `
1997
- }]
1998
- }] });
1999
-
2000
- class MultiselectType extends FieldType {
2001
- get options$() {
2002
- const options = this.props['options'];
2003
- return isObservable(options) ? options : of(options || []);
2004
2364
  }
2005
- defaultOptions = {
2006
- props: {
2007
- options: [],
2008
- optionLabel: 'label',
2009
- optionValue: 'value',
2010
- placeholder: 'Select',
2011
- filter: false,
2012
- maxSelectedLabels: 3,
2013
- showToggleAll: true,
2014
- virtualScroll: false
2365
+ hasStateChanged(current, next) {
2366
+ // Quick reference check
2367
+ if (current === next)
2368
+ return false;
2369
+ // Check essential properties that affect rendering
2370
+ return current.loading !== next.loading ||
2371
+ current.activeTab !== next.activeTab ||
2372
+ current.hasChanges !== next.hasChanges ||
2373
+ current.mode !== next.mode ||
2374
+ current.isNew !== next.isNew ||
2375
+ !this.areTabsEqual(current.tabs, next.tabs) ||
2376
+ this.detectChanges(current.currentData, next.currentData);
2377
+ }
2378
+ areTabsEqual(current, next) {
2379
+ if (current === next)
2380
+ return true;
2381
+ if (current.length !== next.length)
2382
+ return false;
2383
+ return current.every((tab, index) => tab.value === next[index].value &&
2384
+ tab.isParentModel === next[index].isParentModel);
2385
+ }
2386
+ constructor(route, router, notificationService, utilsService) {
2387
+ super(utilsService, notificationService);
2388
+ this.route = route;
2389
+ this.router = router;
2390
+ this.notificationService = notificationService;
2391
+ this.utilsService = utilsService;
2392
+ }
2393
+ ngOnInit() {
2394
+ this.initializeFormWithErrorHandling();
2395
+ this.setupStateSubscription();
2396
+ }
2397
+ initializeFormWithErrorHandling() {
2398
+ // Initialize tabs first
2399
+ const tabs = this.getTabs();
2400
+ this.state = {
2401
+ ...this.state,
2402
+ tabs,
2403
+ activeTab: tabs[0]?.value || ''
2404
+ };
2405
+ // Then initialize form
2406
+ this.route.queryParams.pipe(take$1(1), tap$1(() => this.state = { ...this.state, loading: true }), tap$1(params => this.initializeForm(params)), map$1(() => void 0), catchError$1(error => {
2407
+ console.error('Error initializing form:', error);
2408
+ this.showError(AppMessages.FORM.ERROR.INIT);
2409
+ return of(void 0);
2410
+ }), finalize(() => this.state = { ...this.state, loading: false }), takeUntil$1(this.destroyed$)).subscribe();
2411
+ }
2412
+ setupStateSubscription() {
2413
+ this.stateManager$.pipe(distinctUntilChanged((prev, curr) => !this.hasStateChanged(prev, curr)), takeUntil$1(this.destroyed$)).subscribe(state => {
2414
+ this.handleStateChange(state);
2415
+ });
2416
+ }
2417
+ handleStateChange(state) {
2418
+ // Track significant state changes
2419
+ const stateUpdates = [];
2420
+ if (state.loading) {
2421
+ stateUpdates.push('loading');
2422
+ }
2423
+ if (state.hasChanges) {
2424
+ stateUpdates.push('hasChanges');
2425
+ }
2426
+ if (state.activeTab) {
2427
+ stateUpdates.push('activeTab');
2428
+ }
2429
+ if (stateUpdates.length > 0) {
2430
+ console.log('[BaseTabbedFormComponent] State updated:', {
2431
+ updates: stateUpdates,
2432
+ activeTab: state.activeTab,
2433
+ loading: state.loading,
2434
+ hasChanges: state.hasChanges
2435
+ });
2436
+ }
2437
+ }
2438
+ initializeFormData() {
2439
+ console.log('[BaseTabbedFormComponent] Initializing form data');
2440
+ console.log('[BaseTabbedFormComponent] Current mode:', this.state.mode);
2441
+ if (this.state.mode === AppConstants.PAGE_MODE.CREATE) {
2442
+ console.log('[BaseTabbedFormComponent] Create mode, using defaults');
2443
+ this.loadDefaultTab();
2444
+ return;
2445
+ }
2446
+ const state = {
2447
+ ...this.router.getCurrentNavigation()?.extras?.state,
2448
+ ...window.history.state
2449
+ };
2450
+ console.log('[BaseTabbedFormComponent] Combined state:', state);
2451
+ if (state?.formData) {
2452
+ console.log('[BaseTabbedFormComponent] Found form data in state:', state.formData);
2453
+ this.patchFormData(state.formData);
2454
+ this.loadDefaultTab();
2455
+ }
2456
+ else {
2457
+ console.log('[BaseTabbedFormComponent] No form data, loading from UUID');
2458
+ this.loadFromUuid();
2459
+ }
2460
+ }
2461
+ loadFromUuid() {
2462
+ this.route.queryParams.pipe(take$1(1), map$1((params) => {
2463
+ const uuid = params[AppConstants.FORM.UUID];
2464
+ if (!uuid) {
2465
+ throw new Error(`UUID is required for ${this.state.mode} mode when no formData is provided`);
2466
+ }
2467
+ return uuid;
2468
+ }), tap$1(() => this.state = { ...this.state, loading: true }), tap$1(uuid => this.loadEntity(uuid)), catchError$1(error => {
2469
+ console.error('Error loading UUID:', error);
2470
+ this.showError(`${AppMessages.FORM.ERROR.LOAD} UUID`);
2471
+ return of(void 0);
2472
+ }), finalize(() => this.state = { ...this.state, loading: false }), takeUntil$1(this.destroyed$)).subscribe();
2473
+ }
2474
+ initializeForm(params) {
2475
+ const routeMode = params[AppConstants.FORM.MODE];
2476
+ try {
2477
+ this.initializeFormMode(routeMode);
2478
+ this.setupFormState();
2479
+ this.initializeFormData();
2480
+ }
2481
+ catch (error) {
2482
+ console.error('Error initializing form:', error);
2483
+ this.showError(`${AppMessages.FORM.ERROR.INIT}: ${error instanceof Error ? error.message : 'Unknown error'}`);
2484
+ }
2485
+ }
2486
+ patchFormData(data) {
2487
+ if (data) {
2488
+ this.state.currentData = { ...data };
2489
+ this.state.originalData = { ...data }; // Create a deep copy to avoid reference issues
2490
+ this.state.isNew = false;
2491
+ }
2492
+ }
2493
+ initializeFormMode(routeMode) {
2494
+ if (!routeMode) {
2495
+ this.state.mode = AppConstants.PAGE_MODE.CREATE;
2496
+ return;
2497
+ }
2498
+ // Validate mode more explicitly
2499
+ if (!this.isValidPageMode(routeMode)) {
2500
+ const validModes = Object.values(AppConstants.PAGE_MODE).join(', ');
2501
+ throw new Error(`Invalid page mode: ${routeMode}. Valid modes are: ${validModes}`);
2502
+ }
2503
+ this.state.mode = routeMode;
2504
+ }
2505
+ setupFormState() {
2506
+ console.log('[BaseTabbedFormComponent] Setting up form state, mode:', this.state.mode);
2507
+ if (this.state.mode === AppConstants.PAGE_MODE.CREATE) {
2508
+ console.log('[BaseTabbedFormComponent] Create mode setup');
2509
+ this.state.isNew = true;
2510
+ this.state.loading = false;
2511
+ this.state.originalData = null;
2512
+ }
2513
+ }
2514
+ loadEntity(uuid) {
2515
+ const entityName = this.getEntityName();
2516
+ this.getEntityService().getById(uuid).pipe(tap$1(() => this.state = {
2517
+ ...this.state,
2518
+ loading: true,
2519
+ isNew: false
2520
+ }), takeUntil$1(this.destroyed$)).subscribe({
2521
+ next: (data) => {
2522
+ this.state = {
2523
+ ...this.state,
2524
+ originalData: data,
2525
+ currentData: { ...data },
2526
+ loading: false
2527
+ };
2528
+ console.log('[BaseTabbedFormComponent] Data loaded:', { original: data, current: this.state.currentData });
2529
+ this.loadDefaultTab();
2530
+ },
2531
+ error: (error) => {
2532
+ console.error('[BaseTabbedFormComponent] Error loading entity:', error);
2533
+ this.showError(`${AppMessages.FORM.ERROR.LOAD} ${entityName}`);
2534
+ this.state = { ...this.state, loading: false };
2535
+ }
2536
+ });
2537
+ }
2538
+ findTabByValue(value) {
2539
+ return this.state.tabs.find((tab) => tab.value === value);
2540
+ }
2541
+ loadDefaultTab() {
2542
+ console.log('[BaseTabbedFormComponent] Loading default tab');
2543
+ const defaultTab = this.state.tabs.find((tab) => tab.isParentModel) || this.state.tabs[0];
2544
+ if (!defaultTab)
2545
+ return;
2546
+ const formData = this.getTabFormData(defaultTab);
2547
+ this.router.navigate([defaultTab.value], {
2548
+ relativeTo: this.route,
2549
+ queryParamsHandling: 'preserve',
2550
+ state: {
2551
+ formData,
2552
+ parentFormData: this.state.currentData,
2553
+ context: ComponentContext.TABBED
2554
+ }
2555
+ });
2556
+ }
2557
+ getTabFormData(tabConfig) {
2558
+ if (!tabConfig)
2559
+ return null;
2560
+ if (tabConfig.isParentModel) {
2561
+ return this.state.currentData;
2562
+ }
2563
+ const fieldName = this.utilsService.toCamelCase(tabConfig.value);
2564
+ const nestedData = this.state.currentData[fieldName];
2565
+ if (this.isTabFormData(nestedData)) {
2566
+ return nestedData;
2567
+ }
2568
+ console.warn(`Invalid tab form data for field: ${fieldName}`, nestedData);
2569
+ return null;
2570
+ }
2571
+ isTabFormData(data) {
2572
+ return data != null &&
2573
+ typeof data === 'object' &&
2574
+ 'uuid' in data &&
2575
+ typeof data.uuid === 'string';
2576
+ }
2577
+ onTabChange(value) {
2578
+ const tabConfig = this.findTabByValue(value);
2579
+ if (!tabConfig) {
2580
+ console.warn(`Tab not found: ${value}`);
2581
+ return;
2582
+ }
2583
+ this.state.activeTab = tabConfig.value;
2584
+ const formData = this.getTabFormData(tabConfig);
2585
+ this.router.navigate([this.state.activeTab], {
2586
+ relativeTo: this.route,
2587
+ queryParamsHandling: 'preserve',
2588
+ state: {
2589
+ formData: formData,
2590
+ parentFormData: this.state.currentData,
2591
+ context: ComponentContext.TABBED
2592
+ }
2593
+ });
2594
+ }
2595
+ hasFormChanges(currentData) {
2596
+ if (this.state.isNew || !this.state.originalData) {
2597
+ return true;
2015
2598
  }
2016
- };
2017
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MultiselectType, deps: null, target: i0.ɵɵFactoryTarget.Component });
2018
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: MultiselectType, isStandalone: true, selector: "formly-field-multiselect", usesInheritance: true, ngImport: i0, template: `
2019
- <ng-container *ngIf="options$ | async as options">
2020
- <p-multiSelect
2021
- [formControl]="$any(formControl)"
2022
- [formlyAttributes]="field"
2023
- [options]="options"
2024
- [optionLabel]="props['optionLabel'] || 'label'"
2025
- [optionValue]="props['optionValue'] || 'value'"
2026
- [placeholder]="props['placeholder'] || 'Select'"
2027
- [filter]="props['filter'] || true"
2028
- [filterBy]="props['filterBy'] || 'label'"
2029
- [maxSelectedLabels]="props['maxSelectedLabels'] || 2"
2030
- [showToggleAll]="props['showToggleAll'] || true"
2031
- [virtualScroll]="props['virtualScroll'] || false"
2032
- [display]="props['display'] || 'chip'"
2033
- styleClass="w-full">
2034
- </p-multiSelect>
2035
- </ng-container>
2036
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$3.AsyncPipe, name: "async" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i4$2.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "style", "styleClass", "panelStyle", "panelStyleClass", "inputId", "disabled", "fluid", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "variant", "appendTo", "dataKey", "name", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "size", "showClear", "autofocus", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "defaultLabel", "placeholder", "options", "filterValue", "itemSize", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }] });
2599
+ return this.detectChanges(currentData, this.state.originalData);
2600
+ }
2601
+ detectChanges(current, original) {
2602
+ try {
2603
+ return JSON.stringify(current) !== JSON.stringify(original);
2604
+ }
2605
+ catch (error) {
2606
+ console.error('[BaseTabbedFormComponent] Error comparing objects:', error);
2607
+ // Fallback to basic comparison if JSON.stringify fails
2608
+ return Object.keys(current).some(key => current[key] !== original[key]);
2609
+ }
2610
+ }
2611
+ ngOnDestroy() {
2612
+ this.destroyed$.next();
2613
+ this.destroyed$.complete();
2614
+ this.stateManager$.complete();
2615
+ }
2616
+ isValidPageMode(mode) {
2617
+ return Object.values(AppConstants.PAGE_MODE).includes(mode);
2618
+ }
2619
+ getTabTitle(data) {
2620
+ throw new Error('Method not implemented.');
2621
+ }
2622
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTabbedFormComponent, deps: [{ token: i1$1.ActivatedRoute }, { token: i1$1.Router }, { token: NotificationService }, { token: UtilsService }], target: i0.ɵɵFactoryTarget.Component });
2623
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: BaseTabbedFormComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
2624
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTabbedFormComponent });
2037
2625
  }
2038
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MultiselectType, decorators: [{
2626
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTabbedFormComponent, decorators: [{
2627
+ type: Injectable
2628
+ }, {
2039
2629
  type: Component,
2040
2630
  args: [{
2041
- selector: 'formly-field-multiselect',
2042
- standalone: true,
2043
- imports: [
2044
- CommonModule,
2045
- ReactiveFormsModule,
2046
- FormlyModule,
2047
- MultiSelectModule
2048
- ],
2049
- template: `
2050
- <ng-container *ngIf="options$ | async as options">
2051
- <p-multiSelect
2052
- [formControl]="$any(formControl)"
2053
- [formlyAttributes]="field"
2054
- [options]="options"
2055
- [optionLabel]="props['optionLabel'] || 'label'"
2056
- [optionValue]="props['optionValue'] || 'value'"
2057
- [placeholder]="props['placeholder'] || 'Select'"
2058
- [filter]="props['filter'] || true"
2059
- [filterBy]="props['filterBy'] || 'label'"
2060
- [maxSelectedLabels]="props['maxSelectedLabels'] || 2"
2061
- [showToggleAll]="props['showToggleAll'] || true"
2062
- [virtualScroll]="props['virtualScroll'] || false"
2063
- [display]="props['display'] || 'chip'"
2064
- styleClass="w-full">
2065
- </p-multiSelect>
2066
- </ng-container>
2067
- `
2631
+ template: ''
2068
2632
  }]
2069
- }] });
2633
+ }], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: NotificationService }, { type: UtilsService }] });
2070
2634
 
2071
- class DateType extends FieldType {
2072
- defaultOptions = {
2073
- props: {
2074
- dateFormat: 'dd-mm-yy',
2075
- showTime: false,
2076
- touchUI: false,
2077
- placeholder: '',
2078
- showButtonBar: true,
2079
- }
2635
+ class BaseListTabbedFormComponent extends BasePageComponent {
2636
+ route;
2637
+ router;
2638
+ notificationService;
2639
+ utilsService;
2640
+ destroyed$ = new Subject();
2641
+ tabState$ = new ReplaySubject(1);
2642
+ state = {
2643
+ form: new FormGroup({}),
2644
+ fields: [],
2645
+ isNew: true,
2646
+ mode: AppConstants.PAGE_MODE.CREATE,
2647
+ currentData: {},
2648
+ originalData: null,
2649
+ hasChanges: false,
2650
+ loading: false,
2651
+ context: ComponentContext.TABBED,
2652
+ activeTab: '',
2653
+ tabs: [],
2654
+ formDataList: []
2080
2655
  };
2081
- onDateSelect(event) {
2082
- this.formatAndSetDate(event);
2656
+ constructor(route, router, notificationService, utilsService) {
2657
+ super(utilsService, notificationService);
2658
+ this.route = route;
2659
+ this.router = router;
2660
+ this.notificationService = notificationService;
2661
+ this.utilsService = utilsService;
2083
2662
  }
2084
- onDateInput(event) {
2085
- const inputValue = event.target.value.trim();
2086
- if (this.isValidDateInput(inputValue, this.props['dateFormat'] || 'dd-mm-yy')) {
2087
- const parsedDate = this.parseDate(inputValue, this.props['dateFormat'] || 'dd-mm-yy');
2088
- if (parsedDate) {
2089
- this.formatAndSetDate(parsedDate);
2663
+ ngOnInit() {
2664
+ this.initializeFormFromQueryParams();
2665
+ this.setupTabStateSubscription();
2666
+ this.setupRouteParamsSubscription();
2667
+ }
2668
+ initializeFormFromQueryParams() {
2669
+ this.route.queryParams.pipe(take$1(1), catchError$1(error => {
2670
+ console.error('Error initializing form:', error);
2671
+ this.showError(AppMessages.FORM.ERROR.INIT);
2672
+ throw error;
2673
+ }), takeUntil$1(this.destroyed$)).subscribe(params => this.initializeForm(params));
2674
+ }
2675
+ setupTabStateSubscription() {
2676
+ this.tabState$.pipe(distinctUntilChanged$1((prev, curr) => prev.activeTab === curr.activeTab &&
2677
+ prev.currentData?.uuid === curr.currentData?.uuid), takeUntil$1(this.destroyed$)).subscribe(state => {
2678
+ this.updateComponentState(state);
2679
+ });
2680
+ }
2681
+ setupRouteParamsSubscription() {
2682
+ this.route.params.pipe(takeUntil$1(this.destroyed$)).subscribe(params => {
2683
+ const uuid = params['id'];
2684
+ if (uuid && this.state.formDataList?.length) {
2685
+ this.handleTabChange(uuid);
2090
2686
  }
2687
+ });
2688
+ }
2689
+ handleTabChange(uuid) {
2690
+ const selectedData = this.findTabData(uuid);
2691
+ if (selectedData) {
2692
+ this.tabState$.next({
2693
+ activeTab: uuid,
2694
+ currentData: selectedData,
2695
+ originalData: { ...selectedData }
2696
+ });
2091
2697
  }
2092
2698
  else {
2093
- this.formControl?.setErrors({ invalidDate: true });
2699
+ console.warn(`[BaseListTabbedFormComponent] Tab data not found for uuid: ${uuid}`);
2094
2700
  }
2095
2701
  }
2096
- formatAndSetDate(date) {
2097
- if (this.formControl) {
2098
- const formattedDate = formatDate(date, 'dd-MM-yyyy', 'en-US');
2099
- this.formControl.setValue(formattedDate);
2702
+ findTabData(uuid) {
2703
+ return this.state.formDataList.find(data => data.uuid === uuid);
2704
+ }
2705
+ updateComponentState(tabState) {
2706
+ this.state = {
2707
+ ...this.state,
2708
+ activeTab: tabState.activeTab,
2709
+ currentData: tabState.currentData,
2710
+ originalData: tabState.originalData
2711
+ };
2712
+ }
2713
+ initializeForm(params) {
2714
+ try {
2715
+ const routeMode = params[AppConstants.FORM.MODE];
2716
+ this.initializeFormMode(routeMode);
2717
+ this.setupFormState();
2718
+ this.initializeListForm();
2719
+ }
2720
+ catch (error) {
2721
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
2722
+ console.error('Error initializing form:', error);
2723
+ this.showError(`${AppMessages.FORM.ERROR.INIT}: ${errorMessage}`);
2724
+ throw error; // Re-throw to trigger error handling in the subscription
2100
2725
  }
2101
2726
  }
2102
- parseDate(value, format) {
2103
- const parts = value.split(/[-/]/); // Split by common delimiters
2104
- if (parts.length === 3) {
2105
- const [day, month, year] = format === 'dd-mm-yy'
2106
- ? [parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10)]
2107
- : [parseInt(parts[1], 10), parseInt(parts[0], 10) - 1, parseInt(parts[2], 10)];
2108
- return new Date(year, month, day);
2727
+ initializeFormMode(routeMode) {
2728
+ if (!routeMode) {
2729
+ this.state.mode = AppConstants.PAGE_MODE.CREATE;
2730
+ return;
2109
2731
  }
2110
- return null;
2732
+ if (!this.isValidPageMode(routeMode)) {
2733
+ const validModes = Object.values(AppConstants.PAGE_MODE).join(', ');
2734
+ throw new Error(`Invalid page mode: ${routeMode}. Valid modes are: ${validModes}`);
2735
+ }
2736
+ this.state.mode = routeMode;
2111
2737
  }
2112
- isValidDateInput(value, format) {
2113
- const dateRegex = format === 'dd-mm-yy'
2114
- ? /^\d{2}-\d{2}-\d{4}$/
2115
- : /^\d{2}\/\d{2}\/\d{4}$/;
2116
- return dateRegex.test(value);
2738
+ setupFormState() {
2739
+ if (this.state.mode === AppConstants.PAGE_MODE.CREATE) {
2740
+ this.state.isNew = true;
2741
+ this.state.loading = false;
2742
+ this.state.originalData = null;
2743
+ }
2117
2744
  }
2118
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DateType, deps: null, target: i0.ɵɵFactoryTarget.Component });
2119
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: DateType, isStandalone: true, selector: "formly-field-date", usesInheritance: true, ngImport: i0, template: `
2120
- <p-datepicker
2121
- [formControl]="$any(formControl)"
2122
- [formlyAttributes]="field"
2123
- [showIcon]="true"
2124
- [dateFormat]="props['dateFormat'] || 'dd-mm-yy'"
2125
- [showTime]="props['showTime'] || false"
2126
- [touchUI]="props['touchUI'] || false"
2127
- [placeholder]="props['placeholder'] || ''"
2128
- [minDate]="props['minDate']"
2129
- [maxDate]="props['maxDate']"
2130
- [showOnFocus]="false"
2131
- [todayButtonStyleClass]="'p-button-secondary'"
2132
- [clearButtonStyleClass]="'p-button-secondary'"
2133
- styleClass="w-full"
2134
- (onSelect)="onDateSelect($event)"
2135
- (onInput)="onDateInput($event)">
2136
- </p-datepicker>
2137
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: DatePickerModule }, { kind: "component", type: i3$2.DatePicker, selector: "p-datePicker, p-datepicker, p-date-picker", inputs: ["iconDisplay", "style", "styleClass", "inputStyle", "inputId", "name", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "disabled", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "fluid", "icon", "appendTo", "readonlyInput", "shortYearCutoff", "monthNavigator", "yearNavigator", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "required", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "variant", "size", "minDate", "maxDate", "disabledDates", "disabledDays", "yearRange", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "locale", "view", "defaultDate"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }] });
2138
- }
2139
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DateType, decorators: [{
2140
- type: Component,
2141
- args: [{
2142
- selector: 'formly-field-date',
2143
- standalone: true,
2144
- imports: [
2145
- CommonModule,
2146
- ReactiveFormsModule,
2147
- FormlyModule,
2148
- DatePickerModule
2149
- ],
2150
- template: `
2151
- <p-datepicker
2152
- [formControl]="$any(formControl)"
2153
- [formlyAttributes]="field"
2154
- [showIcon]="true"
2155
- [dateFormat]="props['dateFormat'] || 'dd-mm-yy'"
2156
- [showTime]="props['showTime'] || false"
2157
- [touchUI]="props['touchUI'] || false"
2158
- [placeholder]="props['placeholder'] || ''"
2159
- [minDate]="props['minDate']"
2160
- [maxDate]="props['maxDate']"
2161
- [showOnFocus]="false"
2162
- [todayButtonStyleClass]="'p-button-secondary'"
2163
- [clearButtonStyleClass]="'p-button-secondary'"
2164
- styleClass="w-full"
2165
- (onSelect)="onDateSelect($event)"
2166
- (onInput)="onDateInput($event)">
2167
- </p-datepicker>
2168
- `
2169
- }]
2170
- }] });
2171
-
2172
- class CheckboxType extends FieldType {
2173
- defaultOptions = {
2174
- props: {
2175
- trueValue: true,
2176
- falseValue: false
2745
+ initializeListForm() {
2746
+ const routerState = this.router.getCurrentNavigation()?.extras?.state || window.history.state;
2747
+ const formDataList = routerState?.formDataList;
2748
+ if (!formDataList?.length) {
2749
+ throw new Error('formDataList is required in state for LIST type');
2177
2750
  }
2178
- };
2179
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CheckboxType, deps: null, target: i0.ɵɵFactoryTarget.Component });
2180
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: CheckboxType, isStandalone: true, selector: "formly-field-checkbox", usesInheritance: true, ngImport: i0, template: `
2181
- <div class="checkbox-container">
2182
- <p-checkbox
2183
- [formControl]="$any(formControl)"
2184
- [formlyAttributes]="field"
2185
- [binary]="!props['options']"
2186
- [trueValue]="props['trueValue']"
2187
- [falseValue]="props['falseValue']">
2188
- {{props['checkboxLabel'] || ''}}
2189
- </p-checkbox>
2190
- </div>
2191
- `, isInline: true, styles: [".checkbox-container{display:flex;align-items:center;height:40px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i3$3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }] });
2192
- }
2193
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CheckboxType, decorators: [{
2194
- type: Component,
2195
- args: [{ selector: 'formly-field-checkbox', standalone: true, imports: [
2196
- CommonModule,
2197
- ReactiveFormsModule,
2198
- FormlyModule,
2199
- CheckboxModule
2200
- ], template: `
2201
- <div class="checkbox-container">
2202
- <p-checkbox
2203
- [formControl]="$any(formControl)"
2204
- [formlyAttributes]="field"
2205
- [binary]="!props['options']"
2206
- [trueValue]="props['trueValue']"
2207
- [falseValue]="props['falseValue']">
2208
- {{props['checkboxLabel'] || ''}}
2209
- </p-checkbox>
2210
- </div>
2211
- `, styles: [".checkbox-container{display:flex;align-items:center;height:40px}\n"] }]
2212
- }] });
2213
-
2214
- class TextareaType extends FieldType {
2215
- defaultOptions = {
2216
- props: {
2217
- rows: 3,
2218
- cols: undefined,
2219
- autoResize: true,
2220
- placeholder: ''
2751
+ this.state.formDataList = [...formDataList]; // Create a new array to avoid reference issues
2752
+ this.state.tabs = this.createTabsFromFormDataList(this.state.formDataList);
2753
+ const firstTab = this.state.tabs[0].value;
2754
+ const firstItem = this.state.formDataList[0];
2755
+ // Initialize tab state
2756
+ this.tabState$.next({
2757
+ activeTab: firstTab,
2758
+ currentData: firstItem,
2759
+ originalData: { ...firstItem }
2760
+ });
2761
+ // Navigate to the first tab
2762
+ this.navigateToTab(firstTab);
2763
+ }
2764
+ createTabsFromFormDataList(formDataList) {
2765
+ return formDataList.map((data) => ({
2766
+ value: data.uuid,
2767
+ title: this.getTabTitle(data)
2768
+ }));
2769
+ }
2770
+ onTabChange(event) {
2771
+ const uuid = event?.toString() || '';
2772
+ console.log('[BaseListTabbedFormComponent] Tab change event:', uuid);
2773
+ const selectedData = this.findTabData(uuid);
2774
+ if (selectedData) {
2775
+ this.handleTabChange(uuid);
2776
+ this.navigateToTab(uuid);
2221
2777
  }
2222
- };
2223
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextareaType, deps: null, target: i0.ɵɵFactoryTarget.Component });
2224
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: TextareaType, isStandalone: true, selector: "formly-field-textarea", usesInheritance: true, ngImport: i0, template: `
2225
- <textarea pInputTextarea
2226
- [formControl]="$any(formControl)"
2227
- [formlyAttributes]="field"
2228
- [rows]="props['rows'] || 3"
2229
- [cols]="props['cols']"
2230
- [autoResize]="props['autoResize']"
2231
- [placeholder]="props['placeholder'] || ''"
2232
- class="w-full">
2233
- </textarea>
2234
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: FormlyModule }, { kind: "directive", type: i2.ɵFormlyAttributes, selector: "[formlyAttributes]", inputs: ["formlyAttributes", "id"] }, { kind: "ngmodule", type: Textarea }, { kind: "directive", type: i3$4.InputTextarea, selector: "[pInputTextarea]", inputs: ["autoResize", "variant", "fluid"], outputs: ["onResize"] }] });
2778
+ else {
2779
+ console.warn(`[BaseListTabbedFormComponent] Tab data not found for uuid: ${uuid}`);
2780
+ }
2781
+ }
2782
+ navigateToTab(uuid) {
2783
+ const selectedData = this.findTabData(uuid);
2784
+ if (!selectedData) {
2785
+ console.error(`[BaseListTabbedFormComponent] Cannot navigate: No data found for uuid ${uuid}`);
2786
+ return;
2787
+ }
2788
+ this.router.navigate([uuid], {
2789
+ relativeTo: this.route,
2790
+ queryParamsHandling: 'preserve',
2791
+ state: {
2792
+ formData: selectedData,
2793
+ context: ComponentContext.TABBED
2794
+ }
2795
+ });
2796
+ }
2797
+ ngOnDestroy() {
2798
+ this.destroyed$.next();
2799
+ this.destroyed$.complete();
2800
+ this.tabState$.complete();
2801
+ }
2802
+ isValidPageMode(mode) {
2803
+ return Object.values(AppConstants.PAGE_MODE).includes(mode);
2804
+ }
2805
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseListTabbedFormComponent, deps: [{ token: i1$1.ActivatedRoute }, { token: i1$1.Router }, { token: NotificationService }, { token: UtilsService }], target: i0.ɵɵFactoryTarget.Component });
2806
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: BaseListTabbedFormComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
2807
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseListTabbedFormComponent });
2235
2808
  }
2236
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: TextareaType, decorators: [{
2809
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseListTabbedFormComponent, decorators: [{
2810
+ type: Injectable
2811
+ }, {
2237
2812
  type: Component,
2238
2813
  args: [{
2239
- selector: 'formly-field-textarea',
2240
- standalone: true,
2241
- imports: [
2242
- CommonModule,
2243
- ReactiveFormsModule,
2244
- FormlyModule,
2245
- Textarea
2246
- ],
2247
- template: `
2248
- <textarea pInputTextarea
2249
- [formControl]="$any(formControl)"
2250
- [formlyAttributes]="field"
2251
- [rows]="props['rows'] || 3"
2252
- [cols]="props['cols']"
2253
- [autoResize]="props['autoResize']"
2254
- [placeholder]="props['placeholder'] || ''"
2255
- class="w-full">
2256
- </textarea>
2257
- `
2814
+ template: ''
2258
2815
  }]
2259
- }] });
2816
+ }], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: NotificationService }, { type: UtilsService }] });
2260
2817
 
2261
- class FormFieldWrapper extends FieldWrapper {
2262
- get errorMessage() {
2263
- if (!this.field.formControl?.errors) {
2264
- return '';
2265
- }
2266
- const firstErrorKey = Object.keys(this.field.formControl.errors)[0];
2267
- return this.props.validation?.messages?.[firstErrorKey] || 'This field is required';
2818
+ /**
2819
+ * Base component for all task detail components in the application.
2820
+ * Uses composition with FormService for form functionality.
2821
+ */
2822
+ class BaseTaskDetailsComponent extends BasePageComponent {
2823
+ route;
2824
+ router;
2825
+ notificationService;
2826
+ utilsService;
2827
+ formlyConfigService;
2828
+ destroyed$ = new Subject();
2829
+ formService;
2830
+ state = {
2831
+ form: new FormGroup({}),
2832
+ fields: [],
2833
+ isNew: true,
2834
+ mode: AppConstants.PAGE_MODE.CREATE,
2835
+ currentData: {},
2836
+ originalData: null,
2837
+ hasChanges: false,
2838
+ loading: false,
2839
+ context: ComponentContext.STANDALONE
2840
+ };
2841
+ constructor(route, router, notificationService, utilsService, formlyConfigService) {
2842
+ super(utilsService, notificationService);
2843
+ this.route = route;
2844
+ this.router = router;
2845
+ this.notificationService = notificationService;
2846
+ this.utilsService = utilsService;
2847
+ this.formlyConfigService = formlyConfigService;
2268
2848
  }
2269
- get showError() {
2270
- if (!this.field.formControl) {
2271
- return false;
2849
+ initializeServices() {
2850
+ return of(void 0).pipe(map(() => {
2851
+ this.formService = new FormService(this.getEntityService(), this.formlyConfigService, this.notificationService, this.getEntityName(), this.getJsonFields(), this.getDropdownOptions());
2852
+ this.state = this.formService.getState();
2853
+ this.loading = this.state.loading;
2854
+ }));
2855
+ }
2856
+ ngOnInit() {
2857
+ this.initializeServices()
2858
+ .pipe(switchMap(() => this.initializeFromRoute()), takeUntil(this.destroyed$))
2859
+ .subscribe({
2860
+ next: () => this.onInitialized(),
2861
+ error: error => this.handleInitError(error)
2862
+ });
2863
+ }
2864
+ initializeFromRoute() {
2865
+ return this.route.queryParams.pipe(map(params => {
2866
+ const mode = params[AppConstants.FORM.MODE];
2867
+ if (mode) {
2868
+ this.formService.setMode(mode);
2869
+ this.state = this.formService.getState();
2870
+ }
2871
+ const uuid = params[AppConstants.FORM.UUID];
2872
+ if (uuid) {
2873
+ this.loadData(uuid);
2874
+ }
2875
+ // Check for context in router state
2876
+ const routerState = window.history.state;
2877
+ if (routerState?.context) {
2878
+ this.state.context = routerState.context;
2879
+ }
2880
+ }));
2881
+ }
2882
+ loadData(uuid) {
2883
+ this.state.loading = true;
2884
+ this.formService.loadData(uuid)
2885
+ .pipe(takeUntil(this.destroyed$), finalize$1(() => {
2886
+ this.state.loading = false;
2887
+ this.state = this.formService.getState();
2888
+ }))
2889
+ .subscribe({
2890
+ error: (error) => this.handleLoadError(error)
2891
+ });
2892
+ }
2893
+ onSubmit() {
2894
+ this.formService.submit()
2895
+ .pipe(takeUntil(this.destroyed$))
2896
+ .subscribe({
2897
+ next: () => this.navigateAfterSubmit(),
2898
+ error: (error) => this.handleSubmitError(error)
2899
+ });
2900
+ }
2901
+ navigateAfterSubmit() {
2902
+ if (this.isInContext(ComponentContext.TABBED)) {
2903
+ return; // Stay on current route when in tab
2272
2904
  }
2273
- const { touched, dirty, invalid } = this.field.formControl;
2274
- return invalid && (touched || dirty);
2905
+ this.router.navigate([`/${this.getEntityName().toLowerCase()}s`]);
2275
2906
  }
2276
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormFieldWrapper, deps: null, target: i0.ɵɵFactoryTarget.Component });
2277
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: FormFieldWrapper, isStandalone: true, selector: "formly-wrapper-form-field", usesInheritance: true, ngImport: i0, template: `
2278
- <div class="field mb-4">
2279
- <label *ngIf="props.label" [for]="id" class="font-medium">
2280
- {{ props.label }}
2281
- <span class="required-star" *ngIf="props.required">*</span>
2282
- <i *ngIf="props.tooltip"
2283
- class="pi pi-info-circle ml-2"
2284
- [pTooltip]="props.tooltip">
2285
- </i>
2286
- </label>
2287
-
2288
- <div [class.p-input-error]="showError">
2289
- <ng-container #fieldComponent></ng-container>
2290
- </div>
2291
-
2292
- <small *ngIf="props.description" class="block mt-1">
2293
- {{ props.description }}
2294
- </small>
2295
-
2296
- <small *ngIf="showError" class="p-error block">
2297
- <i class="pi pi-exclamation-circle mr-2"></i>
2298
- {{ errorMessage }}
2299
- </small>
2300
- </div>
2301
- `, isInline: true, styles: [":is() .required-star{color:var(--red-500);margin-left:.25rem}:is() .p-input-error :where(input,.p-inputtext){border-color:var(--red-500)}:is() .p-input-error :where(input:enabled:focus,.p-inputtext:enabled:focus){border-color:var(--red-500);box-shadow:0 0 0 1px var(--red-500)}:is() .p-error{color:var(--red-500);margin-top:.5rem}:is() small{color:var(--text-color-secondary)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] });
2907
+ onInitialized() {
2908
+ console.log('[BaseTaskDetailsComponent] Initialized successfully');
2909
+ }
2910
+ handleInitError(error) {
2911
+ console.error('Initialization error:', error);
2912
+ this.notificationService.showError(AppMessages.FORM.ERROR.INIT);
2913
+ this.state.loading = false;
2914
+ }
2915
+ handleLoadError(error) {
2916
+ console.error('Load error:', error);
2917
+ this.notificationService.showError(`Failed to load ${this.getEntityName()}`);
2918
+ }
2919
+ handleSubmitError(error) {
2920
+ console.error('Submit error:', error);
2921
+ const errorMessage = error.error?.message || `Failed to save ${this.getEntityName()}`;
2922
+ this.showError(errorMessage);
2923
+ }
2924
+ isInContext(contextType) {
2925
+ return this.state.context === contextType;
2926
+ }
2927
+ ngOnDestroy() {
2928
+ this.destroyed$.next();
2929
+ this.destroyed$.complete();
2930
+ }
2931
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTaskDetailsComponent, deps: [{ token: i1$1.ActivatedRoute }, { token: i1$1.Router }, { token: NotificationService }, { token: UtilsService }, { token: FormlyConfigService }], target: i0.ɵɵFactoryTarget.Directive });
2932
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.6", type: BaseTaskDetailsComponent, isStandalone: true, usesInheritance: true, ngImport: i0 });
2302
2933
  }
2303
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: FormFieldWrapper, decorators: [{
2304
- type: Component,
2305
- args: [{ selector: 'formly-wrapper-form-field', standalone: true, imports: [CommonModule, TooltipModule], template: `
2306
- <div class="field mb-4">
2307
- <label *ngIf="props.label" [for]="id" class="font-medium">
2308
- {{ props.label }}
2309
- <span class="required-star" *ngIf="props.required">*</span>
2310
- <i *ngIf="props.tooltip"
2311
- class="pi pi-info-circle ml-2"
2312
- [pTooltip]="props.tooltip">
2313
- </i>
2314
- </label>
2315
-
2316
- <div [class.p-input-error]="showError">
2317
- <ng-container #fieldComponent></ng-container>
2318
- </div>
2319
-
2320
- <small *ngIf="props.description" class="block mt-1">
2321
- {{ props.description }}
2322
- </small>
2323
-
2324
- <small *ngIf="showError" class="p-error block">
2325
- <i class="pi pi-exclamation-circle mr-2"></i>
2326
- {{ errorMessage }}
2327
- </small>
2328
- </div>
2329
- `, styles: [":is() .required-star{color:var(--red-500);margin-left:.25rem}:is() .p-input-error :where(input,.p-inputtext){border-color:var(--red-500)}:is() .p-input-error :where(input:enabled:focus,.p-inputtext:enabled:focus){border-color:var(--red-500);box-shadow:0 0 0 1px var(--red-500)}:is() .p-error{color:var(--red-500);margin-top:.5rem}:is() small{color:var(--text-color-secondary)}\n"] }]
2330
- }] });
2331
-
2332
- const FORMLY_TYPES = {
2333
- TEXT: 'text',
2334
- NUMBER: 'number',
2335
- DATE: 'date',
2336
- CHECKBOX: 'checkbox',
2337
- RADIO: 'radio',
2338
- SELECT: 'select',
2339
- MULTISELECT: 'multiselect',
2340
- TEXTAREA: 'textarea'
2341
- };
2342
- const FORMLY_WRAPPERS = {
2343
- FORM_FIELD: 'form-field'
2344
- };
2934
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: BaseTaskDetailsComponent, decorators: [{
2935
+ type: Directive
2936
+ }], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: NotificationService }, { type: UtilsService }, { type: FormlyConfigService }] });
2345
2937
 
2346
- class CoreFormlyModule {
2347
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CoreFormlyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2348
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: CoreFormlyModule, imports: [i2.FormlyModule], exports: [FormlyModule] });
2349
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CoreFormlyModule, imports: [FormlyModule.forRoot({
2350
- types: [
2351
- { name: FORMLY_TYPES.TEXT, component: TextInputType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2352
- { name: FORMLY_TYPES.NUMBER, component: NumericInputType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2353
- { name: FORMLY_TYPES.DATE, component: DateType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2354
- { name: FORMLY_TYPES.CHECKBOX, component: CheckboxType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2355
- { name: FORMLY_TYPES.RADIO, component: RadioType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2356
- { name: FORMLY_TYPES.SELECT, component: SelectType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2357
- { name: FORMLY_TYPES.MULTISELECT, component: MultiselectType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2358
- { name: FORMLY_TYPES.TEXTAREA, component: TextareaType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] }, // Added textarea type
2359
- ],
2360
- wrappers: [
2361
- { name: FORMLY_WRAPPERS.FORM_FIELD, component: FormFieldWrapper }
2362
- ]
2363
- }), FormlyModule] });
2938
+ class IndianDatePipe {
2939
+ datePipe;
2940
+ constructor(datePipe) {
2941
+ this.datePipe = datePipe;
2942
+ }
2943
+ transform(value, format = 'default') {
2944
+ if (!value)
2945
+ return null;
2946
+ // First validate dd-MM-yyyy format
2947
+ const dateRegex = /^(0[1-9]|[12][0-9]|3[01])-(0[1-9]|1[0-2])-\d{4}$/;
2948
+ if (!dateRegex.test(value)) {
2949
+ throw new Error(`Invalid date format: ${value}. Expected format: dd-MM-yyyy`);
2950
+ }
2951
+ // Validate real date
2952
+ const [day, month, year] = value.split('-').map(Number);
2953
+ const date = new Date(year, month - 1, day);
2954
+ if (date.getFullYear() !== year ||
2955
+ date.getMonth() !== month - 1 ||
2956
+ date.getDate() !== day) {
2957
+ throw new Error(`Invalid date: ${value}`);
2958
+ }
2959
+ // Apply requested format
2960
+ switch (format) {
2961
+ case 'short':
2962
+ // Pad with leading zeros for consistent format
2963
+ const paddedDay = day.toString().padStart(2, '0');
2964
+ const paddedMonth = month.toString().padStart(2, '0');
2965
+ return `${paddedDay}-${paddedMonth}-${year}`; // 10-03-2025
2966
+ case 'medium':
2967
+ return this.datePipe.transform(date, 'dd MMM yyyy'); // 10 Jan 2019
2968
+ default:
2969
+ return value; // original dd-MM-yyyy
2970
+ }
2971
+ }
2972
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: IndianDatePipe, deps: [{ token: i1$3.DatePipe }], target: i0.ɵɵFactoryTarget.Pipe });
2973
+ static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: IndianDatePipe, isStandalone: true, name: "indianDate" });
2364
2974
  }
2365
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CoreFormlyModule, decorators: [{
2366
- type: NgModule,
2975
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: IndianDatePipe, decorators: [{
2976
+ type: Pipe,
2367
2977
  args: [{
2368
- imports: [
2369
- FormlyModule.forRoot({
2370
- types: [
2371
- { name: FORMLY_TYPES.TEXT, component: TextInputType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2372
- { name: FORMLY_TYPES.NUMBER, component: NumericInputType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2373
- { name: FORMLY_TYPES.DATE, component: DateType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2374
- { name: FORMLY_TYPES.CHECKBOX, component: CheckboxType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2375
- { name: FORMLY_TYPES.RADIO, component: RadioType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2376
- { name: FORMLY_TYPES.SELECT, component: SelectType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2377
- { name: FORMLY_TYPES.MULTISELECT, component: MultiselectType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] },
2378
- { name: FORMLY_TYPES.TEXTAREA, component: TextareaType, wrappers: [FORMLY_WRAPPERS.FORM_FIELD] }, // Added textarea type
2379
- ],
2380
- wrappers: [
2381
- { name: FORMLY_WRAPPERS.FORM_FIELD, component: FormFieldWrapper }
2382
- ]
2383
- })
2384
- ],
2385
- exports: [FormlyModule]
2978
+ name: 'indianDate',
2979
+ standalone: true
2386
2980
  }]
2387
- }] });
2981
+ }], ctorParameters: () => [{ type: i1$3.DatePipe }] });
2388
2982
 
2389
2983
  /*
2390
2984
  * Public API Surface of core-lib
2391
2985
  */
2392
- // Services
2986
+ // Constants
2393
2987
 
2394
2988
  /**
2395
2989
  * Generated bundle index. Do not edit.
2396
2990
  */
2397
2991
 
2398
- export { AppConstants, AppMessages, BaseCrudService, BaseFormComponent, BaseListPageComponent, BasePageComponent, BaseTabbedFormComponent, CheckboxType, ComponentContext, CoreFormlyModule, DateType, EntityRegistryService, EnumRegistryService, FormFieldWrapper, FormlyConfigService, HttpUtilityService, IndianDatePipe, MultiselectType, NotificationService, NumericInputType, RadioType, ReferenceDataProviderService, SelectType, TabbedFormType, TextInputType, UtilsService };
2992
+ export { AppConstants, AppMessages, BaseCrudService, BaseFormComponent, BaseListPageComponent, BaseListTabbedFormComponent, BasePageComponent, BaseTabbedFormComponent, BaseTaskDetailsComponent, BaseTaskService, ComponentContext, CoreFormlyModule, FormService, FormlyConfigService, HttpUtilityService, IndianDatePipe, KEYCLOAK_CONFIG, KeycloakAuthGuard, KeycloakService, NotificationService, TabbedFormType, TaskRole, UtilsService };
2399
2993
  //# sourceMappingURL=codex-ts-core-lib.mjs.map