@seniorsistemas/components-ai 0.0.0-master-d4a804fe
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -0
- package/CONTRIBUTING.md +317 -0
- package/README.md +161 -0
- package/docs/API.md +486 -0
- package/docs/COMPONENTS.md +272 -0
- package/docs/EXAMPLES.md +559 -0
- package/docs/MIGRATION.md +367 -0
- package/esm2022/lib/angular-components.module.mjs +25 -0
- package/esm2022/lib/components/breadcrumb/breadcrumb.component.mjs +121 -0
- package/esm2022/lib/components/bulk-delete-dialog/bulk-delete-dialog.component.mjs +176 -0
- package/esm2022/lib/components/dynamic-form/dynamic-form.component.mjs +625 -0
- package/esm2022/lib/components/dynamic-form/fields/dynamic-field-date.component.mjs +86 -0
- package/esm2022/lib/components/dynamic-form/fields/dynamic-field-dropdown.component.mjs +103 -0
- package/esm2022/lib/components/dynamic-form/fields/dynamic-field-lookup.component.mjs +599 -0
- package/esm2022/lib/components/dynamic-form/fields/dynamic-field-number.component.mjs +92 -0
- package/esm2022/lib/components/dynamic-form/fields/dynamic-field-text.component.mjs +163 -0
- package/esm2022/lib/components/dynamic-form/fields/dynamic-field-textarea.component.mjs +81 -0
- package/esm2022/lib/components/dynamic-form/fields/dynamic-field-wrapper.component.mjs +93 -0
- package/esm2022/lib/components/dynamic-form/fields/index.mjs +8 -0
- package/esm2022/lib/components/dynamic-form/models/dynamic-form.models.mjs +2 -0
- package/esm2022/lib/components/export-dialog/export-dialog.component.mjs +219 -0
- package/esm2022/lib/config/translation.config.mjs +70 -0
- package/esm2022/lib/directives/cep-mask.directive.mjs +79 -0
- package/esm2022/lib/directives/document-mask.directive.mjs +62 -0
- package/esm2022/lib/directives/phone-mask.directive.mjs +59 -0
- package/esm2022/lib/interceptors/api.interceptor.mjs +55 -0
- package/esm2022/lib/models/base-entity.interface.mjs +2 -0
- package/esm2022/lib/models/entity-list.config.mjs +2 -0
- package/esm2022/lib/pipes/translate.pipe.mjs +74 -0
- package/esm2022/lib/services/auth.service.mjs +169 -0
- package/esm2022/lib/services/cookie.service.mjs +90 -0
- package/esm2022/lib/services/entity.service.mjs +208 -0
- package/esm2022/lib/services/mask.service.mjs +121 -0
- package/esm2022/lib/services/permission.service.mjs +180 -0
- package/esm2022/lib/services/senior-token.service.mjs +209 -0
- package/esm2022/lib/services/theme.service.mjs +85 -0
- package/esm2022/lib/services/translation.service.mjs +232 -0
- package/esm2022/public-api.mjs +90 -0
- package/esm2022/seniorsistemas-components-ai.mjs +5 -0
- package/fesm2022/seniorsistemas-components-ai.mjs +4006 -0
- package/fesm2022/seniorsistemas-components-ai.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/angular-components.module.d.ts +13 -0
- package/lib/components/breadcrumb/breadcrumb.component.d.ts +23 -0
- package/lib/components/bulk-delete-dialog/bulk-delete-dialog.component.d.ts +46 -0
- package/lib/components/dynamic-form/dynamic-form.component.d.ts +97 -0
- package/lib/components/dynamic-form/fields/dynamic-field-date.component.d.ts +16 -0
- package/lib/components/dynamic-form/fields/dynamic-field-dropdown.component.d.ts +17 -0
- package/lib/components/dynamic-form/fields/dynamic-field-lookup.component.d.ts +52 -0
- package/lib/components/dynamic-form/fields/dynamic-field-number.component.d.ts +16 -0
- package/lib/components/dynamic-form/fields/dynamic-field-text.component.d.ts +17 -0
- package/lib/components/dynamic-form/fields/dynamic-field-textarea.component.d.ts +16 -0
- package/lib/components/dynamic-form/fields/dynamic-field-wrapper.component.d.ts +20 -0
- package/lib/components/dynamic-form/fields/index.d.ts +7 -0
- package/lib/components/dynamic-form/models/dynamic-form.models.d.ts +178 -0
- package/lib/components/export-dialog/export-dialog.component.d.ts +56 -0
- package/lib/config/translation.config.d.ts +24 -0
- package/lib/directives/cep-mask.directive.d.ts +13 -0
- package/lib/directives/document-mask.directive.d.ts +19 -0
- package/lib/directives/phone-mask.directive.d.ts +11 -0
- package/lib/interceptors/api.interceptor.d.ts +2 -0
- package/lib/models/base-entity.interface.d.ts +7 -0
- package/lib/models/entity-list.config.d.ts +161 -0
- package/lib/pipes/translate.pipe.d.ts +21 -0
- package/lib/services/auth.service.d.ts +82 -0
- package/lib/services/cookie.service.d.ts +31 -0
- package/lib/services/entity.service.d.ts +99 -0
- package/lib/services/mask.service.d.ts +36 -0
- package/lib/services/permission.service.d.ts +91 -0
- package/lib/services/senior-token.service.d.ts +70 -0
- package/lib/services/theme.service.d.ts +16 -0
- package/lib/services/translation.service.d.ts +54 -0
- package/package.json +53 -0
- package/public-api.d.ts +17 -0
- package/src/lib/styles/entity-list.shared.scss +383 -0
- package/src/lib/styles/index.scss +10 -0
|
@@ -0,0 +1,4006 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { NgModule, Injectable, Optional, Inject, EventEmitter, Component, Input, Output, Pipe, Directive, HostListener, ViewEncapsulation, inject } from '@angular/core';
|
|
3
|
+
import * as i2 from '@angular/common';
|
|
4
|
+
import { CommonModule, DOCUMENT } from '@angular/common';
|
|
5
|
+
import * as i1 from '@angular/common/http';
|
|
6
|
+
import { HttpParams } from '@angular/common/http';
|
|
7
|
+
import { throwError, BehaviorSubject, of, from, concatMap, catchError as catchError$1 } from 'rxjs';
|
|
8
|
+
import { catchError, map, filter } from 'rxjs/operators';
|
|
9
|
+
import * as i2$1 from '@angular/forms';
|
|
10
|
+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
11
|
+
import * as i3 from 'primeng/dialog';
|
|
12
|
+
import { DialogModule } from 'primeng/dialog';
|
|
13
|
+
import * as i5$1 from 'primeng/button';
|
|
14
|
+
import { ButtonModule } from 'primeng/button';
|
|
15
|
+
import * as i6 from 'primeng/radiobutton';
|
|
16
|
+
import { RadioButtonModule } from 'primeng/radiobutton';
|
|
17
|
+
import * as i7 from 'primeng/checkbox';
|
|
18
|
+
import { CheckboxModule } from 'primeng/checkbox';
|
|
19
|
+
import { DividerModule } from 'primeng/divider';
|
|
20
|
+
import * as XLSX from 'xlsx';
|
|
21
|
+
import { jsPDF } from 'jspdf';
|
|
22
|
+
import autoTable from 'jspdf-autotable';
|
|
23
|
+
import * as i5 from 'primeng/api';
|
|
24
|
+
import * as i4 from 'primeng/progressbar';
|
|
25
|
+
import { ProgressBarModule } from 'primeng/progressbar';
|
|
26
|
+
import * as i5$2 from 'primeng/tag';
|
|
27
|
+
import { TagModule } from 'primeng/tag';
|
|
28
|
+
import * as i1$1 from '@angular/router';
|
|
29
|
+
import { NavigationEnd, RouterModule, Router } from '@angular/router';
|
|
30
|
+
import * as i2$2 from 'primeng/breadcrumb';
|
|
31
|
+
import { BreadcrumbModule } from 'primeng/breadcrumb';
|
|
32
|
+
import * as i4$2 from 'primeng/panel';
|
|
33
|
+
import { PanelModule } from 'primeng/panel';
|
|
34
|
+
import * as i7$1 from 'primeng/drawer';
|
|
35
|
+
import { DrawerModule } from 'primeng/drawer';
|
|
36
|
+
import * as i4$1 from 'primeng/inputtext';
|
|
37
|
+
import { InputTextModule } from 'primeng/inputtext';
|
|
38
|
+
import * as i3$1 from 'primeng/inputnumber';
|
|
39
|
+
import { InputNumberModule } from 'primeng/inputnumber';
|
|
40
|
+
import * as i3$2 from 'primeng/calendar';
|
|
41
|
+
import { CalendarModule } from 'primeng/calendar';
|
|
42
|
+
import * as i3$3 from 'primeng/dropdown';
|
|
43
|
+
import { DropdownModule } from 'primeng/dropdown';
|
|
44
|
+
import * as i8 from 'primeng/table';
|
|
45
|
+
import { TableModule } from 'primeng/table';
|
|
46
|
+
import * as i9 from 'primeng/tooltip';
|
|
47
|
+
import { TooltipModule } from 'primeng/tooltip';
|
|
48
|
+
import { InputTextarea } from 'primeng/inputtextarea';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Módulo principal da biblioteca @seniorsistemas/components-ia
|
|
52
|
+
*
|
|
53
|
+
* Este módulo será populado conforme os componentes forem migrados.
|
|
54
|
+
* Por enquanto, é um módulo vazio para permitir o build inicial.
|
|
55
|
+
*/
|
|
56
|
+
class AngularComponentsModule {
|
|
57
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AngularComponentsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
58
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: AngularComponentsModule, imports: [CommonModule] });
|
|
59
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AngularComponentsModule, imports: [CommonModule] });
|
|
60
|
+
}
|
|
61
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AngularComponentsModule, decorators: [{
|
|
62
|
+
type: NgModule,
|
|
63
|
+
args: [{
|
|
64
|
+
declarations: [],
|
|
65
|
+
imports: [
|
|
66
|
+
CommonModule
|
|
67
|
+
],
|
|
68
|
+
exports: []
|
|
69
|
+
}]
|
|
70
|
+
}] });
|
|
71
|
+
|
|
72
|
+
class EntityService {
|
|
73
|
+
http;
|
|
74
|
+
translationService;
|
|
75
|
+
queriesUrl = '';
|
|
76
|
+
constructor(http, translationService) {
|
|
77
|
+
this.http = http;
|
|
78
|
+
this.translationService = translationService;
|
|
79
|
+
// Initialize queriesUrl in ngOnInit or a separate method
|
|
80
|
+
// since abstract properties can't be accessed in constructor
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Initialize the service - call this after setting actionsUrl
|
|
84
|
+
*/
|
|
85
|
+
initializeService() {
|
|
86
|
+
this.queriesUrl = this.actionsUrl.replace('actions', 'queries');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Convert ListParams to HttpParams for GET requests
|
|
90
|
+
*/
|
|
91
|
+
getListQueryParams(listParams) {
|
|
92
|
+
const { page = 0, size = 10, sort = [], filterQuery = '', displayFields = [] } = listParams;
|
|
93
|
+
let params = new HttpParams();
|
|
94
|
+
params = params.append('size', String(size));
|
|
95
|
+
params = params.append('offset', String(page));
|
|
96
|
+
if (sort && sort.length) {
|
|
97
|
+
const orderBy = sort
|
|
98
|
+
.map((s) => {
|
|
99
|
+
let order = '';
|
|
100
|
+
if (s.order === 1)
|
|
101
|
+
order = ' asc';
|
|
102
|
+
else if (s.order === -1)
|
|
103
|
+
order = ' desc';
|
|
104
|
+
return `${s.field}${order}`;
|
|
105
|
+
})
|
|
106
|
+
.join(', ');
|
|
107
|
+
params = params.append('orderby', orderBy);
|
|
108
|
+
}
|
|
109
|
+
if (filterQuery) {
|
|
110
|
+
params = params.append('filter', filterQuery);
|
|
111
|
+
}
|
|
112
|
+
if (displayFields && displayFields.length) {
|
|
113
|
+
params = params.append('displayfields', displayFields.join(','));
|
|
114
|
+
}
|
|
115
|
+
return params;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Convert ListParams to body parameters for POST requests
|
|
119
|
+
*/
|
|
120
|
+
getBodyParams(listParams) {
|
|
121
|
+
const { page = 0, size = 10, sort = [], filterQuery = '', displayFields = [] } = listParams;
|
|
122
|
+
const bodyParams = {};
|
|
123
|
+
bodyParams.size = size;
|
|
124
|
+
bodyParams.offset = page;
|
|
125
|
+
if (sort && sort.length) {
|
|
126
|
+
bodyParams.orderBy = sort
|
|
127
|
+
.map((s) => {
|
|
128
|
+
let order = '';
|
|
129
|
+
if (s.order === 1)
|
|
130
|
+
order = ' asc';
|
|
131
|
+
else if (s.order === -1)
|
|
132
|
+
order = ' desc';
|
|
133
|
+
return `${s.field}${order}`;
|
|
134
|
+
})
|
|
135
|
+
.join(', ');
|
|
136
|
+
}
|
|
137
|
+
if (filterQuery) {
|
|
138
|
+
bodyParams.filter = filterQuery;
|
|
139
|
+
}
|
|
140
|
+
if (displayFields && displayFields.length) {
|
|
141
|
+
bodyParams.displayfields = displayFields.join(',');
|
|
142
|
+
}
|
|
143
|
+
return bodyParams;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Default error handler
|
|
147
|
+
*/
|
|
148
|
+
handleError = (error) => {
|
|
149
|
+
let errorMessage = 'An error occurred';
|
|
150
|
+
if (this.translationService) {
|
|
151
|
+
errorMessage = this.translationService.translate('error.generic');
|
|
152
|
+
}
|
|
153
|
+
if (error.status) {
|
|
154
|
+
switch (error.status) {
|
|
155
|
+
case 401:
|
|
156
|
+
errorMessage = this.translationService
|
|
157
|
+
? this.translationService.translate('error.unauthorized')
|
|
158
|
+
: 'Unauthorized';
|
|
159
|
+
break;
|
|
160
|
+
case 403:
|
|
161
|
+
errorMessage = this.translationService
|
|
162
|
+
? this.translationService.translate('error.forbidden')
|
|
163
|
+
: 'Forbidden';
|
|
164
|
+
break;
|
|
165
|
+
case 404:
|
|
166
|
+
errorMessage = this.translationService
|
|
167
|
+
? this.translationService.translate('error.not_found')
|
|
168
|
+
: 'Not found';
|
|
169
|
+
break;
|
|
170
|
+
case 500:
|
|
171
|
+
errorMessage = this.translationService
|
|
172
|
+
? this.translationService.translate('error.server_error')
|
|
173
|
+
: 'Server error';
|
|
174
|
+
break;
|
|
175
|
+
default:
|
|
176
|
+
if (error.error && error.error.message) {
|
|
177
|
+
errorMessage = error.error.message;
|
|
178
|
+
}
|
|
179
|
+
else if (error.statusText) {
|
|
180
|
+
errorMessage = error.statusText;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else if (error.message) {
|
|
185
|
+
errorMessage = error.message;
|
|
186
|
+
}
|
|
187
|
+
console.error('EntityService Error:', error);
|
|
188
|
+
return throwError(() => new Error(errorMessage));
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* List entities with pagination, sorting, and filtering
|
|
192
|
+
*/
|
|
193
|
+
list(listParams = {}) {
|
|
194
|
+
const params = this.getListQueryParams(listParams);
|
|
195
|
+
return this.http.get(this.entityUrl, { params })
|
|
196
|
+
.pipe(catchError(this.handleError));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Get entity by ID
|
|
200
|
+
*/
|
|
201
|
+
get(id) {
|
|
202
|
+
return this.http.get(`${this.entityUrl}/${id}`)
|
|
203
|
+
.pipe(catchError(this.handleError));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Create new entity
|
|
207
|
+
*/
|
|
208
|
+
insert(entity) {
|
|
209
|
+
return this.http.post(this.entityUrl, entity)
|
|
210
|
+
.pipe(catchError(this.handleError));
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Update existing entity
|
|
214
|
+
*/
|
|
215
|
+
update(id, entity) {
|
|
216
|
+
return this.http.put(`${this.entityUrl}/${id}`, entity)
|
|
217
|
+
.pipe(catchError(this.handleError));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Delete entity by ID
|
|
221
|
+
*/
|
|
222
|
+
delete(id) {
|
|
223
|
+
return this.http.delete(`${this.entityUrl}/${id}`, { observe: 'response' })
|
|
224
|
+
.pipe(map(() => undefined), catchError(this.handleError));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Execute custom filter action
|
|
228
|
+
*/
|
|
229
|
+
listCustomFilter(listParams, action) {
|
|
230
|
+
const bodyParams = this.getBodyParams(listParams);
|
|
231
|
+
return this.http.post(`${this.actionsUrl}/${action}`, bodyParams)
|
|
232
|
+
.pipe(catchError(this.handleError));
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Execute custom query
|
|
236
|
+
*/
|
|
237
|
+
executeQuery(queryName, params = {}) {
|
|
238
|
+
return this.http.post(`${this.queriesUrl}/${queryName}`, params)
|
|
239
|
+
.pipe(catchError(this.handleError));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Execute custom action
|
|
243
|
+
*/
|
|
244
|
+
executeAction(actionName, params = {}) {
|
|
245
|
+
return this.http.post(`${this.actionsUrl}/${actionName}`, params)
|
|
246
|
+
.pipe(catchError(this.handleError));
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Check if entity exists by ID
|
|
250
|
+
*/
|
|
251
|
+
exists(id) {
|
|
252
|
+
return this.http.head(`${this.entityUrl}/${id}`)
|
|
253
|
+
.pipe(map(() => true), catchError((error) => {
|
|
254
|
+
if (error.status === 404) {
|
|
255
|
+
return [false];
|
|
256
|
+
}
|
|
257
|
+
return this.handleError(error);
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Bulk delete entities
|
|
262
|
+
*/
|
|
263
|
+
bulkDelete(ids) {
|
|
264
|
+
return this.http.post(`${this.actionsUrl}/bulkDelete`, { ids }, { observe: 'response' })
|
|
265
|
+
.pipe(map(() => undefined), catchError(this.handleError));
|
|
266
|
+
}
|
|
267
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EntityService, deps: "invalid", target: i0.ɵɵFactoryTarget.Injectable });
|
|
268
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EntityService });
|
|
269
|
+
}
|
|
270
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: EntityService, decorators: [{
|
|
271
|
+
type: Injectable
|
|
272
|
+
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined }] });
|
|
273
|
+
|
|
274
|
+
class CookieService {
|
|
275
|
+
/**
|
|
276
|
+
* Obtém o valor de um cookie pelo nome
|
|
277
|
+
*/
|
|
278
|
+
getCookie(name) {
|
|
279
|
+
if (typeof document === 'undefined') {
|
|
280
|
+
return null; // SSR protection
|
|
281
|
+
}
|
|
282
|
+
const nameEQ = name + '=';
|
|
283
|
+
const cookies = document.cookie.split(';');
|
|
284
|
+
for (let cookie of cookies) {
|
|
285
|
+
cookie = cookie.trim();
|
|
286
|
+
if (cookie.indexOf(nameEQ) === 0) {
|
|
287
|
+
const value = decodeURIComponent(cookie.substring(nameEQ.length));
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Define um cookie
|
|
295
|
+
*/
|
|
296
|
+
setCookie(name, value, days, path = '/') {
|
|
297
|
+
if (typeof document === 'undefined') {
|
|
298
|
+
return; // SSR protection
|
|
299
|
+
}
|
|
300
|
+
let expires = '';
|
|
301
|
+
if (days) {
|
|
302
|
+
const date = new Date();
|
|
303
|
+
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
304
|
+
expires = '; expires=' + date.toUTCString();
|
|
305
|
+
}
|
|
306
|
+
document.cookie = `${name}=${encodeURIComponent(value)}${expires}; path=${path}`;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Remove um cookie
|
|
310
|
+
*/
|
|
311
|
+
deleteCookie(name, path = '/') {
|
|
312
|
+
this.setCookie(name, '', -1, path);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Verifica se um cookie existe
|
|
316
|
+
*/
|
|
317
|
+
hasCookie(name) {
|
|
318
|
+
return this.getCookie(name) !== null;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Obtém todos os cookies como objeto
|
|
322
|
+
*/
|
|
323
|
+
getAllCookies() {
|
|
324
|
+
if (typeof document === 'undefined') {
|
|
325
|
+
return {}; // SSR protection
|
|
326
|
+
}
|
|
327
|
+
const cookies = {};
|
|
328
|
+
const cookieArray = document.cookie.split(';');
|
|
329
|
+
for (let cookie of cookieArray) {
|
|
330
|
+
cookie = cookie.trim();
|
|
331
|
+
const [name, value] = cookie.split('=');
|
|
332
|
+
if (name && value) {
|
|
333
|
+
cookies[name] = decodeURIComponent(value);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return cookies;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Debug: Lista todos os cookies disponíveis
|
|
340
|
+
*/
|
|
341
|
+
debugAllCookies() {
|
|
342
|
+
console.log('[CookieService] All available cookies:');
|
|
343
|
+
const allCookies = this.getAllCookies();
|
|
344
|
+
Object.keys(allCookies).forEach(name => {
|
|
345
|
+
const value = allCookies[name];
|
|
346
|
+
console.log(`[CookieService] ${name}: ${value.length > 100 ? value.substring(0, 100) + '...' : value}`);
|
|
347
|
+
});
|
|
348
|
+
if (Object.keys(allCookies).length === 0) {
|
|
349
|
+
console.log('[CookieService] No cookies found');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CookieService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
353
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CookieService, providedIn: 'root' });
|
|
354
|
+
}
|
|
355
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CookieService, decorators: [{
|
|
356
|
+
type: Injectable,
|
|
357
|
+
args: [{
|
|
358
|
+
providedIn: 'root'
|
|
359
|
+
}]
|
|
360
|
+
}] });
|
|
361
|
+
|
|
362
|
+
class AuthService {
|
|
363
|
+
cookieService;
|
|
364
|
+
TOKEN_COOKIE_NAME = 'com.senior.token';
|
|
365
|
+
API_URL_COOKIE_NAME = 'com.senior.base.url';
|
|
366
|
+
API_PATH_SUFFIX = '/t/senior.com.br/bridge/1.0/rest';
|
|
367
|
+
currentUserSubject = new BehaviorSubject(null);
|
|
368
|
+
currentUser$ = this.currentUserSubject.asObservable();
|
|
369
|
+
constructor(cookieService) {
|
|
370
|
+
this.cookieService = cookieService;
|
|
371
|
+
this.loadUserFromCookie();
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Load user token from cookie
|
|
375
|
+
*/
|
|
376
|
+
loadUserFromCookie() {
|
|
377
|
+
const tokenString = this.cookieService.getCookie(this.TOKEN_COOKIE_NAME);
|
|
378
|
+
if (tokenString) {
|
|
379
|
+
try {
|
|
380
|
+
const token = JSON.parse(tokenString);
|
|
381
|
+
if (this.isTokenValid(token)) {
|
|
382
|
+
this.currentUserSubject.next(token);
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
this.currentUserSubject.next(null);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
console.error('[AuthService] Error parsing token from cookie:', error);
|
|
390
|
+
this.currentUserSubject.next(null);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Get current user token
|
|
396
|
+
*/
|
|
397
|
+
getCurrentUser() {
|
|
398
|
+
return this.currentUserSubject.value;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Get access token
|
|
402
|
+
*/
|
|
403
|
+
getAccessToken() {
|
|
404
|
+
const user = this.getCurrentUser();
|
|
405
|
+
return user?.access_token || null;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Get token type (e.g., "Bearer")
|
|
409
|
+
*/
|
|
410
|
+
getTokenType() {
|
|
411
|
+
const user = this.getCurrentUser();
|
|
412
|
+
return user?.token_type || 'Bearer';
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get refresh token
|
|
416
|
+
*/
|
|
417
|
+
getRefreshToken() {
|
|
418
|
+
const user = this.getCurrentUser();
|
|
419
|
+
return user?.refresh_token || null;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Get API base URL from cookie
|
|
423
|
+
*/
|
|
424
|
+
getApiBaseUrl() {
|
|
425
|
+
const baseUrl = this.cookieService.getCookie(this.API_URL_COOKIE_NAME);
|
|
426
|
+
// In development, use proxy
|
|
427
|
+
if (!baseUrl) {
|
|
428
|
+
// Check if we're in development mode
|
|
429
|
+
const isDevelopment = !window.location.hostname.includes('senior.com.br');
|
|
430
|
+
if (isDevelopment) {
|
|
431
|
+
return '/api';
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
// Remove trailing slash from base URL
|
|
436
|
+
const cleanBaseUrl = baseUrl.replace(/\/$/, '');
|
|
437
|
+
// Combine with API path suffix
|
|
438
|
+
return `${cleanBaseUrl}${this.API_PATH_SUFFIX}`;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Check if user is authenticated
|
|
442
|
+
*/
|
|
443
|
+
isAuthenticated() {
|
|
444
|
+
const user = this.getCurrentUser();
|
|
445
|
+
return user !== null && this.isTokenValid(user);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Check if token is valid (not expired)
|
|
449
|
+
*/
|
|
450
|
+
isTokenValid(token) {
|
|
451
|
+
if (!token || !token.access_token) {
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
// Check if token is expired
|
|
455
|
+
const now = Math.floor(Date.now() / 1000);
|
|
456
|
+
const tokenExpiry = token.expires_in;
|
|
457
|
+
// If expires_in is a timestamp, compare directly
|
|
458
|
+
// If it's a duration, we'd need the token creation time
|
|
459
|
+
// For now, assume it's a timestamp
|
|
460
|
+
// If expires_in is very large (like a duration in seconds), skip expiry check
|
|
461
|
+
if (tokenExpiry > 1000000000) {
|
|
462
|
+
// Looks like a timestamp
|
|
463
|
+
return tokenExpiry > now;
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
// Looks like a duration, assume token is valid
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Check if refresh token is valid
|
|
472
|
+
*/
|
|
473
|
+
isRefreshTokenValid() {
|
|
474
|
+
const user = this.getCurrentUser();
|
|
475
|
+
if (!user || !user.refresh_token) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
const now = Math.floor(Date.now() / 1000);
|
|
479
|
+
const refreshExpiry = user.refresh_expires_in;
|
|
480
|
+
return refreshExpiry > now;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Get user locale
|
|
484
|
+
*/
|
|
485
|
+
getUserLocale() {
|
|
486
|
+
const user = this.getCurrentUser();
|
|
487
|
+
return user?.locale || 'pt-BR';
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Get user full name
|
|
491
|
+
*/
|
|
492
|
+
getUserFullName() {
|
|
493
|
+
const user = this.getCurrentUser();
|
|
494
|
+
return user?.fullName || '';
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Get user email
|
|
498
|
+
*/
|
|
499
|
+
getUserEmail() {
|
|
500
|
+
const user = this.getCurrentUser();
|
|
501
|
+
return user?.email || '';
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Get tenant name
|
|
505
|
+
*/
|
|
506
|
+
getTenantName() {
|
|
507
|
+
const user = this.getCurrentUser();
|
|
508
|
+
return user?.tenantName || '';
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Get username
|
|
512
|
+
*/
|
|
513
|
+
getUsername() {
|
|
514
|
+
const user = this.getCurrentUser();
|
|
515
|
+
return user?.username || '';
|
|
516
|
+
}
|
|
517
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, deps: [{ token: CookieService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
518
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, providedIn: 'root' });
|
|
519
|
+
}
|
|
520
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, decorators: [{
|
|
521
|
+
type: Injectable,
|
|
522
|
+
args: [{
|
|
523
|
+
providedIn: 'root'
|
|
524
|
+
}]
|
|
525
|
+
}], ctorParameters: () => [{ type: CookieService }] });
|
|
526
|
+
|
|
527
|
+
class PermissionService {
|
|
528
|
+
http;
|
|
529
|
+
authService;
|
|
530
|
+
PERMISSION_ENDPOINT = 'platform/authorization/queries/checkAccess';
|
|
531
|
+
permissionCache = new Map();
|
|
532
|
+
cacheExpiry = new Map();
|
|
533
|
+
CACHE_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
534
|
+
constructor(http, authService) {
|
|
535
|
+
this.http = http;
|
|
536
|
+
this.authService = authService;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Check access for a single permission
|
|
540
|
+
*/
|
|
541
|
+
checkAccess(resource, action, context) {
|
|
542
|
+
const cacheKey = this.getCacheKey(resource, action, context);
|
|
543
|
+
// Check cache first
|
|
544
|
+
const cachedResult = this.getCachedPermission(cacheKey);
|
|
545
|
+
if (cachedResult !== null) {
|
|
546
|
+
return of(cachedResult.allowed);
|
|
547
|
+
}
|
|
548
|
+
// Make API request
|
|
549
|
+
const request = {
|
|
550
|
+
permissions: [{ resource, action, context }]
|
|
551
|
+
};
|
|
552
|
+
return this.http.post(this.PERMISSION_ENDPOINT, request).pipe(map(response => {
|
|
553
|
+
if (response.results && response.results.length > 0) {
|
|
554
|
+
const result = response.results[0];
|
|
555
|
+
this.cachePermission(cacheKey, result);
|
|
556
|
+
return result.allowed;
|
|
557
|
+
}
|
|
558
|
+
return false;
|
|
559
|
+
}), catchError(error => {
|
|
560
|
+
console.error('Permission check failed:', error);
|
|
561
|
+
// On error, deny access by default
|
|
562
|
+
return of(false);
|
|
563
|
+
}));
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Check access for multiple permissions
|
|
567
|
+
*/
|
|
568
|
+
checkMultipleAccess(permissions) {
|
|
569
|
+
const uncachedPermissions = [];
|
|
570
|
+
const results = [];
|
|
571
|
+
// Check cache for each permission
|
|
572
|
+
permissions.forEach(permission => {
|
|
573
|
+
const cacheKey = this.getCacheKey(permission.resource, permission.action, permission.context);
|
|
574
|
+
const cachedResult = this.getCachedPermission(cacheKey);
|
|
575
|
+
if (cachedResult !== null) {
|
|
576
|
+
results.push(cachedResult);
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
uncachedPermissions.push(permission);
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
// If all permissions are cached, return immediately
|
|
583
|
+
if (uncachedPermissions.length === 0) {
|
|
584
|
+
return of(results);
|
|
585
|
+
}
|
|
586
|
+
// Make API request for uncached permissions
|
|
587
|
+
const request = {
|
|
588
|
+
permissions: uncachedPermissions
|
|
589
|
+
};
|
|
590
|
+
return this.http.post(this.PERMISSION_ENDPOINT, request).pipe(map(response => {
|
|
591
|
+
if (response.results) {
|
|
592
|
+
// Cache the new results
|
|
593
|
+
response.results.forEach(result => {
|
|
594
|
+
const cacheKey = this.getCacheKey(result.resource, result.action);
|
|
595
|
+
this.cachePermission(cacheKey, result);
|
|
596
|
+
});
|
|
597
|
+
// Combine cached and new results
|
|
598
|
+
return [...results, ...response.results];
|
|
599
|
+
}
|
|
600
|
+
return results;
|
|
601
|
+
}), catchError(error => {
|
|
602
|
+
console.error('Multiple permission check failed:', error);
|
|
603
|
+
// On error, return cached results and deny uncached ones
|
|
604
|
+
const deniedResults = uncachedPermissions.map(permission => ({
|
|
605
|
+
allowed: false,
|
|
606
|
+
resource: permission.resource,
|
|
607
|
+
action: permission.action,
|
|
608
|
+
reason: 'Permission check failed'
|
|
609
|
+
}));
|
|
610
|
+
return of([...results, ...deniedResults]);
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Check if user can read a resource
|
|
615
|
+
*/
|
|
616
|
+
canRead(resource, context) {
|
|
617
|
+
return this.checkAccess(resource, 'read', context);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Check if user can write to a resource
|
|
621
|
+
*/
|
|
622
|
+
canWrite(resource, context) {
|
|
623
|
+
return this.checkAccess(resource, 'write', context);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Check if user can delete a resource
|
|
627
|
+
*/
|
|
628
|
+
canDelete(resource, context) {
|
|
629
|
+
return this.checkAccess(resource, 'delete', context);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Check if user can execute an action on a resource
|
|
633
|
+
*/
|
|
634
|
+
canExecute(resource, action, context) {
|
|
635
|
+
return this.checkAccess(resource, action, context);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Clear permission cache
|
|
639
|
+
*/
|
|
640
|
+
clearCache() {
|
|
641
|
+
this.permissionCache.clear();
|
|
642
|
+
this.cacheExpiry.clear();
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Clear expired cache entries
|
|
646
|
+
*/
|
|
647
|
+
clearExpiredCache() {
|
|
648
|
+
const now = Date.now();
|
|
649
|
+
const expiredKeys = [];
|
|
650
|
+
this.cacheExpiry.forEach((expiry, key) => {
|
|
651
|
+
if (expiry < now) {
|
|
652
|
+
expiredKeys.push(key);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
expiredKeys.forEach(key => {
|
|
656
|
+
this.permissionCache.delete(key);
|
|
657
|
+
this.cacheExpiry.delete(key);
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Generate cache key for permission
|
|
662
|
+
*/
|
|
663
|
+
getCacheKey(resource, action, context) {
|
|
664
|
+
const contextStr = context ? JSON.stringify(context) : '';
|
|
665
|
+
return `${resource}:${action}:${contextStr}`;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Get cached permission result
|
|
669
|
+
*/
|
|
670
|
+
getCachedPermission(cacheKey) {
|
|
671
|
+
const expiry = this.cacheExpiry.get(cacheKey);
|
|
672
|
+
if (expiry && expiry > Date.now()) {
|
|
673
|
+
return this.permissionCache.get(cacheKey) || null;
|
|
674
|
+
}
|
|
675
|
+
// Remove expired entry
|
|
676
|
+
this.permissionCache.delete(cacheKey);
|
|
677
|
+
this.cacheExpiry.delete(cacheKey);
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Cache permission result
|
|
682
|
+
*/
|
|
683
|
+
cachePermission(cacheKey, result) {
|
|
684
|
+
this.permissionCache.set(cacheKey, result);
|
|
685
|
+
this.cacheExpiry.set(cacheKey, Date.now() + this.CACHE_DURATION);
|
|
686
|
+
}
|
|
687
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionService, deps: [{ token: i1.HttpClient }, { token: 'AuthService', optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
688
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionService, providedIn: 'root' });
|
|
689
|
+
}
|
|
690
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PermissionService, decorators: [{
|
|
691
|
+
type: Injectable,
|
|
692
|
+
args: [{
|
|
693
|
+
providedIn: 'root'
|
|
694
|
+
}]
|
|
695
|
+
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
|
|
696
|
+
type: Optional
|
|
697
|
+
}, {
|
|
698
|
+
type: Inject,
|
|
699
|
+
args: ['AuthService']
|
|
700
|
+
}] }] });
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Serviço para gerenciar o token Senior e extrair informações específicas
|
|
704
|
+
* para o sistema de traduções.
|
|
705
|
+
*
|
|
706
|
+
* Nota: Este serviço foca especificamente na extração do locale e informações
|
|
707
|
+
* básicas do usuário. Para funcionalidades completas de autenticação,
|
|
708
|
+
* use o AuthService que tem uma interface mais robusta.
|
|
709
|
+
*/
|
|
710
|
+
class SeniorTokenService {
|
|
711
|
+
cookieService;
|
|
712
|
+
SENIOR_TOKEN_COOKIE = 'com.senior.token';
|
|
713
|
+
constructor(cookieService) {
|
|
714
|
+
this.cookieService = cookieService;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Obtém o token Senior completo do cookie
|
|
718
|
+
*/
|
|
719
|
+
getSeniorToken() {
|
|
720
|
+
// Tentar o nome padrão primeiro
|
|
721
|
+
let token = this.cookieService.getCookie(this.SENIOR_TOKEN_COOKIE);
|
|
722
|
+
if (!token) {
|
|
723
|
+
// Tentar nomes alternativos comuns
|
|
724
|
+
const alternativeNames = [
|
|
725
|
+
'com.senior.token',
|
|
726
|
+
'senior.token',
|
|
727
|
+
'senior_token',
|
|
728
|
+
'seniorToken',
|
|
729
|
+
'token',
|
|
730
|
+
'authToken',
|
|
731
|
+
'auth_token'
|
|
732
|
+
];
|
|
733
|
+
for (const name of alternativeNames) {
|
|
734
|
+
token = this.cookieService.getCookie(name);
|
|
735
|
+
if (token) {
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return token;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Decodifica e obtém os dados do token Senior
|
|
744
|
+
*/
|
|
745
|
+
getSeniorTokenData() {
|
|
746
|
+
const token = this.getSeniorToken();
|
|
747
|
+
if (!token) {
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
let tokenData;
|
|
752
|
+
// Primeiro, tentar como JSON direto (mais provável baseado na informação fornecida)
|
|
753
|
+
try {
|
|
754
|
+
tokenData = JSON.parse(token);
|
|
755
|
+
}
|
|
756
|
+
catch (jsonError) {
|
|
757
|
+
// Se falhar, tentar como JWT
|
|
758
|
+
if (token.includes('.')) {
|
|
759
|
+
const parts = token.split('.');
|
|
760
|
+
if (parts.length >= 2) {
|
|
761
|
+
const payload = parts[1];
|
|
762
|
+
// Adicionar padding se necessário
|
|
763
|
+
const paddedPayload = payload + '='.repeat((4 - payload.length % 4) % 4);
|
|
764
|
+
const decodedPayload = atob(paddedPayload);
|
|
765
|
+
tokenData = JSON.parse(decodedPayload);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
throw new Error('Invalid JWT format');
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
throw jsonError; // Re-throw original JSON error
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return tokenData;
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
console.error('[SeniorTokenService] Error parsing Senior token:', error);
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Obtém o locale do usuário do token Senior
|
|
784
|
+
*/
|
|
785
|
+
getUserLocale() {
|
|
786
|
+
const tokenData = this.getSeniorTokenData();
|
|
787
|
+
if (!tokenData) {
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
790
|
+
// O locale deve estar no mesmo nível do access_token
|
|
791
|
+
if (tokenData.locale) {
|
|
792
|
+
return tokenData.locale;
|
|
793
|
+
}
|
|
794
|
+
// Fallback: tentar outros campos comuns
|
|
795
|
+
const possibleLocaleFields = [
|
|
796
|
+
'language',
|
|
797
|
+
'lang',
|
|
798
|
+
'user_locale',
|
|
799
|
+
'userLocale',
|
|
800
|
+
'preferred_language',
|
|
801
|
+
'preferredLanguage'
|
|
802
|
+
];
|
|
803
|
+
for (const field of possibleLocaleFields) {
|
|
804
|
+
if (tokenData[field]) {
|
|
805
|
+
return tokenData[field];
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Obtém informações do usuário do token Senior
|
|
812
|
+
*/
|
|
813
|
+
getUserInfo() {
|
|
814
|
+
const tokenData = this.getSeniorTokenData();
|
|
815
|
+
if (!tokenData) {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
// Baseado na estrutura do AuthService, os campos devem estar no nível raiz
|
|
819
|
+
return {
|
|
820
|
+
userId: tokenData.userId || tokenData['sub'] || tokenData['user_id'] || tokenData.username,
|
|
821
|
+
userName: tokenData.userName || tokenData['name'] || tokenData['user_name'] || tokenData.username || tokenData.fullName,
|
|
822
|
+
locale: tokenData.locale,
|
|
823
|
+
email: tokenData.email,
|
|
824
|
+
fullName: tokenData.fullName
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Verifica se o usuário está logado (token existe e é válido)
|
|
829
|
+
*/
|
|
830
|
+
isUserLoggedIn() {
|
|
831
|
+
const tokenData = this.getSeniorTokenData();
|
|
832
|
+
if (!tokenData) {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
// Verificar expiração se existir
|
|
836
|
+
if (tokenData['exp']) {
|
|
837
|
+
const now = Math.floor(Date.now() / 1000);
|
|
838
|
+
if (tokenData['exp'] < now) {
|
|
839
|
+
console.warn('Senior token has expired');
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return true;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Monitora mudanças no cookie do token (para detectar login/logout)
|
|
847
|
+
*/
|
|
848
|
+
watchTokenChanges(callback) {
|
|
849
|
+
let lastToken = this.getSeniorToken();
|
|
850
|
+
const checkToken = () => {
|
|
851
|
+
const currentToken = this.getSeniorToken();
|
|
852
|
+
if (currentToken !== lastToken) {
|
|
853
|
+
lastToken = currentToken;
|
|
854
|
+
const tokenData = this.getSeniorTokenData();
|
|
855
|
+
callback(tokenData);
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
// Verificar a cada 5 segundos
|
|
859
|
+
setInterval(checkToken, 5000);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Valida se o token tem a estrutura esperada do Senior
|
|
863
|
+
*/
|
|
864
|
+
validateTokenStructure() {
|
|
865
|
+
const tokenData = this.getSeniorTokenData();
|
|
866
|
+
const issues = [];
|
|
867
|
+
if (!tokenData) {
|
|
868
|
+
issues.push('Token not found or could not be parsed');
|
|
869
|
+
return { isValid: false, issues };
|
|
870
|
+
}
|
|
871
|
+
// Verificar campos obrigatórios esperados
|
|
872
|
+
const requiredFields = ['access_token'];
|
|
873
|
+
const recommendedFields = ['locale', 'username', 'email'];
|
|
874
|
+
for (const field of requiredFields) {
|
|
875
|
+
if (!tokenData[field]) {
|
|
876
|
+
issues.push(`Missing required field: ${field}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
for (const field of recommendedFields) {
|
|
880
|
+
if (!tokenData[field]) {
|
|
881
|
+
issues.push(`Missing recommended field: ${field}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// Verificar se locale está presente e é válido
|
|
885
|
+
if (tokenData.locale) {
|
|
886
|
+
// Importar a função de mapeamento seria circular, então fazer verificação básica
|
|
887
|
+
const supportedLocales = ['pt-BR', 'en-US', 'es-ES', 'pt', 'en', 'es'];
|
|
888
|
+
const locale = tokenData.locale;
|
|
889
|
+
const isSupported = supportedLocales.some(supportedLocale => locale.toLowerCase().includes(supportedLocale.toLowerCase()));
|
|
890
|
+
if (!isSupported) {
|
|
891
|
+
issues.push(`Locale '${locale}' might not be supported`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const isValid = issues.filter(issue => issue.includes('required')).length === 0;
|
|
895
|
+
console.log('[SeniorTokenService] Token validation result:', { isValid, issues });
|
|
896
|
+
return { isValid, issues };
|
|
897
|
+
}
|
|
898
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SeniorTokenService, deps: [{ token: CookieService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
899
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SeniorTokenService, providedIn: 'root' });
|
|
900
|
+
}
|
|
901
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SeniorTokenService, decorators: [{
|
|
902
|
+
type: Injectable,
|
|
903
|
+
args: [{
|
|
904
|
+
providedIn: 'root'
|
|
905
|
+
}]
|
|
906
|
+
}], ctorParameters: () => [{ type: CookieService }] });
|
|
907
|
+
|
|
908
|
+
class ThemeService {
|
|
909
|
+
document;
|
|
910
|
+
isDarkModeSubject = new BehaviorSubject(false);
|
|
911
|
+
isDarkMode$ = this.isDarkModeSubject.asObservable();
|
|
912
|
+
mediaQuery;
|
|
913
|
+
constructor(document) {
|
|
914
|
+
this.document = document;
|
|
915
|
+
// Create media query to watch for system theme changes (kept for compatibility)
|
|
916
|
+
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
917
|
+
// Check for saved theme preference, default to light theme
|
|
918
|
+
const savedTheme = localStorage.getItem('theme');
|
|
919
|
+
if (savedTheme === 'dark') {
|
|
920
|
+
// Only use dark theme if explicitly saved as dark
|
|
921
|
+
this.setTheme(true);
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
// Default to light theme, ignoring system preference
|
|
925
|
+
this.setTheme(false);
|
|
926
|
+
// Set default theme preference if not exists
|
|
927
|
+
if (!savedTheme) {
|
|
928
|
+
localStorage.setItem('theme', 'light');
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
followSystemTheme() {
|
|
933
|
+
// This method is kept for compatibility but no longer used by default
|
|
934
|
+
// Set theme based on system preference only when explicitly requested
|
|
935
|
+
this.setTheme(this.mediaQuery.matches);
|
|
936
|
+
// Listen for system theme changes only when in auto mode
|
|
937
|
+
this.mediaQuery.addEventListener('change', (e) => {
|
|
938
|
+
const savedTheme = localStorage.getItem('theme');
|
|
939
|
+
if (savedTheme === 'auto') {
|
|
940
|
+
this.setTheme(e.matches);
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
toggleTheme() {
|
|
945
|
+
const currentTheme = this.isDarkModeSubject.value;
|
|
946
|
+
this.setTheme(!currentTheme);
|
|
947
|
+
// When manually toggling, save the specific preference
|
|
948
|
+
localStorage.setItem('theme', !currentTheme ? 'dark' : 'light');
|
|
949
|
+
}
|
|
950
|
+
setThemeMode(mode) {
|
|
951
|
+
localStorage.setItem('theme', mode);
|
|
952
|
+
if (mode === 'auto') {
|
|
953
|
+
this.followSystemTheme();
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
this.setTheme(mode === 'dark');
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
setTheme(isDark) {
|
|
960
|
+
this.isDarkModeSubject.next(isDark);
|
|
961
|
+
// Apply theme to document
|
|
962
|
+
if (isDark) {
|
|
963
|
+
this.document.documentElement.classList.add('app-dark');
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
this.document.documentElement.classList.remove('app-dark');
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
get isDarkMode() {
|
|
970
|
+
return this.isDarkModeSubject.value;
|
|
971
|
+
}
|
|
972
|
+
get currentThemeMode() {
|
|
973
|
+
const savedTheme = localStorage.getItem('theme');
|
|
974
|
+
return savedTheme || 'light'; // Default to light instead of auto
|
|
975
|
+
}
|
|
976
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThemeService, deps: [{ token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
977
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThemeService, providedIn: 'root' });
|
|
978
|
+
}
|
|
979
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ThemeService, decorators: [{
|
|
980
|
+
type: Injectable,
|
|
981
|
+
args: [{
|
|
982
|
+
providedIn: 'root'
|
|
983
|
+
}]
|
|
984
|
+
}], ctorParameters: () => [{ type: Document, decorators: [{
|
|
985
|
+
type: Inject,
|
|
986
|
+
args: [DOCUMENT]
|
|
987
|
+
}] }] });
|
|
988
|
+
|
|
989
|
+
const SUPPORTED_LANGUAGES = [
|
|
990
|
+
{
|
|
991
|
+
code: 'pt-BR',
|
|
992
|
+
name: 'Português (Brasil)',
|
|
993
|
+
flag: '🇧🇷',
|
|
994
|
+
aliases: ['pt', 'pt_BR', 'portuguese', 'br']
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
code: 'en-US',
|
|
998
|
+
name: 'English (US)',
|
|
999
|
+
flag: '🇺🇸',
|
|
1000
|
+
aliases: ['en', 'en_US', 'english', 'us']
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
code: 'es-ES',
|
|
1004
|
+
name: 'Español (España)',
|
|
1005
|
+
flag: '🇪🇸',
|
|
1006
|
+
aliases: ['es', 'es_ES', 'spanish', 'spain']
|
|
1007
|
+
}
|
|
1008
|
+
];
|
|
1009
|
+
const DEFAULT_LANGUAGE = 'pt-BR';
|
|
1010
|
+
const TRANSLATION_CONFIG = {
|
|
1011
|
+
supportedLanguages: SUPPORTED_LANGUAGES,
|
|
1012
|
+
defaultLanguage: DEFAULT_LANGUAGE,
|
|
1013
|
+
fallbackLanguage: 'pt-BR',
|
|
1014
|
+
storageKey: 'selectedLanguage',
|
|
1015
|
+
assetsPath: './app/locale'
|
|
1016
|
+
};
|
|
1017
|
+
/**
|
|
1018
|
+
* Mapeia um locale do token Senior para um código de idioma suportado
|
|
1019
|
+
*/
|
|
1020
|
+
function mapTokenLocaleToLanguage(tokenLocale) {
|
|
1021
|
+
if (!tokenLocale)
|
|
1022
|
+
return null;
|
|
1023
|
+
// Normalizar o locale (remover case sensitivity e caracteres especiais)
|
|
1024
|
+
const normalizedLocale = tokenLocale.toLowerCase().replace(/[_-]/g, '');
|
|
1025
|
+
// Procurar correspondência exata primeiro
|
|
1026
|
+
for (const lang of SUPPORTED_LANGUAGES) {
|
|
1027
|
+
if (lang.code.toLowerCase().replace(/[_-]/g, '') === normalizedLocale) {
|
|
1028
|
+
return lang.code;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
// Procurar por aliases
|
|
1032
|
+
for (const lang of SUPPORTED_LANGUAGES) {
|
|
1033
|
+
if (lang.aliases) {
|
|
1034
|
+
for (const alias of lang.aliases) {
|
|
1035
|
+
if (alias.toLowerCase().replace(/[_-]/g, '') === normalizedLocale) {
|
|
1036
|
+
return lang.code;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
// Tentar correspondência parcial (apenas o idioma, ignorando país)
|
|
1042
|
+
const languageOnly = normalizedLocale.substring(0, 2);
|
|
1043
|
+
for (const lang of SUPPORTED_LANGUAGES) {
|
|
1044
|
+
const langCode = lang.code.toLowerCase().substring(0, 2);
|
|
1045
|
+
if (langCode === languageOnly) {
|
|
1046
|
+
return lang.code;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
console.warn(`Unsupported locale from token: ${tokenLocale}`);
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Obtém informações de um idioma pelo código
|
|
1054
|
+
*/
|
|
1055
|
+
function getLanguageInfo(code) {
|
|
1056
|
+
return SUPPORTED_LANGUAGES.find(lang => lang.code === code) || null;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
class TranslationService {
|
|
1060
|
+
seniorTokenService;
|
|
1061
|
+
currentLanguage = new BehaviorSubject(TRANSLATION_CONFIG.defaultLanguage);
|
|
1062
|
+
translations = {};
|
|
1063
|
+
loadingLanguages = new Set();
|
|
1064
|
+
translationLoader;
|
|
1065
|
+
constructor(seniorTokenService, loader) {
|
|
1066
|
+
this.seniorTokenService = seniorTokenService;
|
|
1067
|
+
this.translationLoader = loader;
|
|
1068
|
+
this.loadStoredLanguage();
|
|
1069
|
+
// Carregar traduções do idioma inicial imediatamente
|
|
1070
|
+
this.loadTranslations();
|
|
1071
|
+
// Monitorar mudanças no token para atualizar idioma
|
|
1072
|
+
this.watchTokenChanges();
|
|
1073
|
+
}
|
|
1074
|
+
get currentLanguage$() {
|
|
1075
|
+
return this.currentLanguage.asObservable();
|
|
1076
|
+
}
|
|
1077
|
+
getCurrentLanguage() {
|
|
1078
|
+
return this.currentLanguage.value;
|
|
1079
|
+
}
|
|
1080
|
+
async setLanguage(language) {
|
|
1081
|
+
this.currentLanguage.next(language);
|
|
1082
|
+
localStorage.setItem(TRANSLATION_CONFIG.storageKey, language);
|
|
1083
|
+
await this.loadTranslations();
|
|
1084
|
+
}
|
|
1085
|
+
async waitForTranslations() {
|
|
1086
|
+
const language = this.currentLanguage.value;
|
|
1087
|
+
if (!this.isLanguageLoaded(language)) {
|
|
1088
|
+
await this.loadTranslations();
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Verifica se as traduções do idioma atual estão carregadas
|
|
1093
|
+
*/
|
|
1094
|
+
isLanguageLoaded(language) {
|
|
1095
|
+
const lang = language || this.currentLanguage.value;
|
|
1096
|
+
return !!this.translations[lang] && Object.keys(this.translations[lang]).length > 0;
|
|
1097
|
+
}
|
|
1098
|
+
translate(key, params) {
|
|
1099
|
+
const currentLang = this.currentLanguage.value;
|
|
1100
|
+
const translations = this.translations[currentLang];
|
|
1101
|
+
if (!translations || Object.keys(translations).length === 0) {
|
|
1102
|
+
// Se as traduções não estão carregadas, tentar carregar de forma assíncrona
|
|
1103
|
+
if (!this.loadingLanguages.has(currentLang)) {
|
|
1104
|
+
this.loadTranslations().catch(error => {
|
|
1105
|
+
console.error('Error loading translations:', error);
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
console.warn(`No translations loaded for language: ${currentLang}`);
|
|
1109
|
+
return key;
|
|
1110
|
+
}
|
|
1111
|
+
// Para estrutura plana, buscar diretamente pela chave
|
|
1112
|
+
const translation = translations[key];
|
|
1113
|
+
if (!translation) {
|
|
1114
|
+
console.warn(`Translation key not found: ${key}`);
|
|
1115
|
+
return key;
|
|
1116
|
+
}
|
|
1117
|
+
if (typeof translation !== 'string') {
|
|
1118
|
+
console.warn(`Translation key is not a string: ${key}`);
|
|
1119
|
+
return key;
|
|
1120
|
+
}
|
|
1121
|
+
// Replace parameters if provided
|
|
1122
|
+
if (params) {
|
|
1123
|
+
return this.replaceParams(translation, params);
|
|
1124
|
+
}
|
|
1125
|
+
return translation;
|
|
1126
|
+
}
|
|
1127
|
+
async loadTranslations() {
|
|
1128
|
+
const language = this.currentLanguage.value;
|
|
1129
|
+
// Se já está carregado, não carregar novamente
|
|
1130
|
+
if (this.isLanguageLoaded(language)) {
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
// Evitar carregamentos duplicados
|
|
1134
|
+
if (this.loadingLanguages.has(language)) {
|
|
1135
|
+
// Aguardar um pouco e tentar novamente
|
|
1136
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1137
|
+
if (this.isLanguageLoaded(language)) {
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
this.loadingLanguages.add(language);
|
|
1142
|
+
try {
|
|
1143
|
+
await this.doLoadTranslations(language);
|
|
1144
|
+
}
|
|
1145
|
+
catch (error) {
|
|
1146
|
+
console.error('Error in loadTranslations:', error);
|
|
1147
|
+
}
|
|
1148
|
+
finally {
|
|
1149
|
+
this.loadingLanguages.delete(language);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
async doLoadTranslations(language) {
|
|
1153
|
+
try {
|
|
1154
|
+
// Se há um loader customizado, usar ele
|
|
1155
|
+
if (this.translationLoader) {
|
|
1156
|
+
const translations = await this.translationLoader.loadTranslations(language);
|
|
1157
|
+
this.translations[language] = translations;
|
|
1158
|
+
console.log(`Translations loaded for ${language}:`, Object.keys(this.translations[language]).length, 'keys');
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
// Caso contrário, avisar que não há traduções
|
|
1162
|
+
console.warn(`No translation loader provided. Translations for ${language} not loaded.`);
|
|
1163
|
+
this.translations[language] = {};
|
|
1164
|
+
}
|
|
1165
|
+
catch (error) {
|
|
1166
|
+
console.error(`Error loading translations for ${language}:`, error);
|
|
1167
|
+
// Fallback para o idioma padrão se houver erro
|
|
1168
|
+
if (language !== TRANSLATION_CONFIG.fallbackLanguage) {
|
|
1169
|
+
try {
|
|
1170
|
+
if (this.translationLoader) {
|
|
1171
|
+
const fallbackTranslations = await this.translationLoader.loadTranslations(TRANSLATION_CONFIG.fallbackLanguage);
|
|
1172
|
+
this.translations[language] = fallbackTranslations;
|
|
1173
|
+
console.log(`Fallback translations loaded for ${language}`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
catch (fallbackError) {
|
|
1177
|
+
console.error(`Error loading fallback translations:`, fallbackError);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
loadStoredLanguage() {
|
|
1183
|
+
// 1. Primeiro, tentar obter do token Senior
|
|
1184
|
+
const tokenLocale = this.seniorTokenService.getUserLocale();
|
|
1185
|
+
if (tokenLocale) {
|
|
1186
|
+
const mappedLanguage = mapTokenLocaleToLanguage(tokenLocale);
|
|
1187
|
+
if (mappedLanguage) {
|
|
1188
|
+
console.log(`Using language from Senior token: ${tokenLocale} -> ${mappedLanguage}`);
|
|
1189
|
+
this.currentLanguage.next(mappedLanguage);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
// 2. Segundo, tentar obter do localStorage
|
|
1194
|
+
const stored = localStorage.getItem(TRANSLATION_CONFIG.storageKey);
|
|
1195
|
+
const supportedCodes = TRANSLATION_CONFIG.supportedLanguages.map(lang => lang.code);
|
|
1196
|
+
if (stored && supportedCodes.includes(stored)) {
|
|
1197
|
+
console.log('Using language from localStorage:', stored);
|
|
1198
|
+
this.currentLanguage.next(stored);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
// 3. Por último, usar idioma padrão
|
|
1202
|
+
console.log('Using default language:', TRANSLATION_CONFIG.defaultLanguage);
|
|
1203
|
+
this.currentLanguage.next(TRANSLATION_CONFIG.defaultLanguage);
|
|
1204
|
+
}
|
|
1205
|
+
replaceParams(text, params) {
|
|
1206
|
+
return text.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
1207
|
+
return params[key] !== undefined ? params[key] : match;
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
getSupportedLanguages() {
|
|
1211
|
+
return TRANSLATION_CONFIG.supportedLanguages;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Força o recarregamento das traduções (útil para desenvolvimento)
|
|
1215
|
+
*/
|
|
1216
|
+
async reloadTranslations() {
|
|
1217
|
+
const language = this.currentLanguage.value;
|
|
1218
|
+
delete this.translations[language];
|
|
1219
|
+
await this.loadTranslations();
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Obtém todas as chaves de tradução carregadas
|
|
1223
|
+
*/
|
|
1224
|
+
getLoadedKeys() {
|
|
1225
|
+
const language = this.currentLanguage.value;
|
|
1226
|
+
const translations = this.translations[language];
|
|
1227
|
+
return translations ? Object.keys(translations) : [];
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Verifica se um código de idioma é suportado
|
|
1231
|
+
*/
|
|
1232
|
+
isValidLanguage(locale) {
|
|
1233
|
+
const supportedCodes = TRANSLATION_CONFIG.supportedLanguages.map(lang => lang.code);
|
|
1234
|
+
return supportedCodes.includes(locale);
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Monitora mudanças no token Senior para atualizar idioma automaticamente
|
|
1238
|
+
*/
|
|
1239
|
+
watchTokenChanges() {
|
|
1240
|
+
this.seniorTokenService.watchTokenChanges((tokenData) => {
|
|
1241
|
+
if (tokenData?.locale) {
|
|
1242
|
+
const mappedLanguage = mapTokenLocaleToLanguage(tokenData.locale);
|
|
1243
|
+
if (mappedLanguage) {
|
|
1244
|
+
const newLocale = mappedLanguage;
|
|
1245
|
+
const currentLocale = this.currentLanguage.value;
|
|
1246
|
+
if (newLocale !== currentLocale) {
|
|
1247
|
+
console.log(`Token locale changed: ${tokenData.locale} -> ${newLocale}`);
|
|
1248
|
+
this.setLanguage(newLocale);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Força atualização do idioma baseado no token atual
|
|
1256
|
+
*/
|
|
1257
|
+
async syncWithToken() {
|
|
1258
|
+
const tokenLocale = this.seniorTokenService.getUserLocale();
|
|
1259
|
+
if (tokenLocale) {
|
|
1260
|
+
const mappedLanguage = mapTokenLocaleToLanguage(tokenLocale);
|
|
1261
|
+
if (mappedLanguage) {
|
|
1262
|
+
const newLocale = mappedLanguage;
|
|
1263
|
+
const currentLocale = this.currentLanguage.value;
|
|
1264
|
+
if (newLocale !== currentLocale) {
|
|
1265
|
+
console.log(`Syncing language with token: ${tokenLocale} -> ${newLocale}`);
|
|
1266
|
+
await this.setLanguage(newLocale);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TranslationService, deps: [{ token: SeniorTokenService }, { token: 'TranslationLoader', optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1272
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TranslationService, providedIn: 'root' });
|
|
1273
|
+
}
|
|
1274
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TranslationService, decorators: [{
|
|
1275
|
+
type: Injectable,
|
|
1276
|
+
args: [{
|
|
1277
|
+
providedIn: 'root'
|
|
1278
|
+
}]
|
|
1279
|
+
}], ctorParameters: () => [{ type: SeniorTokenService }, { type: undefined, decorators: [{
|
|
1280
|
+
type: Optional
|
|
1281
|
+
}, {
|
|
1282
|
+
type: Inject,
|
|
1283
|
+
args: ['TranslationLoader']
|
|
1284
|
+
}] }] });
|
|
1285
|
+
|
|
1286
|
+
class ExportDialogComponent {
|
|
1287
|
+
translationService;
|
|
1288
|
+
visible = false;
|
|
1289
|
+
allRecords = [];
|
|
1290
|
+
selectedRecords = [];
|
|
1291
|
+
statusLabels;
|
|
1292
|
+
translationPrefix = 'crmx.business';
|
|
1293
|
+
set availableColumns(columns) {
|
|
1294
|
+
if (columns && columns.length > 0) {
|
|
1295
|
+
this.columns = columns;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
visibleChange = new EventEmitter();
|
|
1299
|
+
exportFormat = 'xlsx';
|
|
1300
|
+
exportScope = 'all';
|
|
1301
|
+
formats = [];
|
|
1302
|
+
scopes = [];
|
|
1303
|
+
columns = [];
|
|
1304
|
+
constructor(translationService) {
|
|
1305
|
+
this.translationService = translationService;
|
|
1306
|
+
this.initializeFormats();
|
|
1307
|
+
this.initializeScopes();
|
|
1308
|
+
}
|
|
1309
|
+
ngOnChanges() {
|
|
1310
|
+
this.initializeFormats();
|
|
1311
|
+
this.initializeScopes();
|
|
1312
|
+
}
|
|
1313
|
+
t(key) {
|
|
1314
|
+
if (this.translationService) {
|
|
1315
|
+
return this.translationService.translate(`${this.translationPrefix}.${key}`);
|
|
1316
|
+
}
|
|
1317
|
+
return key;
|
|
1318
|
+
}
|
|
1319
|
+
initializeFormats() {
|
|
1320
|
+
this.formats = [
|
|
1321
|
+
{
|
|
1322
|
+
value: 'xlsx',
|
|
1323
|
+
label: 'Excel',
|
|
1324
|
+
icon: 'pi-file-excel',
|
|
1325
|
+
description: this.t('export_excel_description')
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
value: 'pdf',
|
|
1329
|
+
label: 'PDF',
|
|
1330
|
+
icon: 'pi-file-pdf',
|
|
1331
|
+
description: this.t('export_pdf_description')
|
|
1332
|
+
}
|
|
1333
|
+
];
|
|
1334
|
+
}
|
|
1335
|
+
initializeScopes() {
|
|
1336
|
+
this.scopes = [
|
|
1337
|
+
{
|
|
1338
|
+
value: 'all',
|
|
1339
|
+
label: this.t('export_all_records'),
|
|
1340
|
+
icon: 'pi-list',
|
|
1341
|
+
description: this.t('export_all_records_description')
|
|
1342
|
+
},
|
|
1343
|
+
{
|
|
1344
|
+
value: 'selected',
|
|
1345
|
+
label: this.t('export_selected_records'),
|
|
1346
|
+
icon: 'pi-check-square',
|
|
1347
|
+
description: this.t('export_selected_records_description')
|
|
1348
|
+
}
|
|
1349
|
+
];
|
|
1350
|
+
}
|
|
1351
|
+
get recordsToExport() {
|
|
1352
|
+
return this.exportScope === 'all' ? this.allRecords : this.selectedRecords;
|
|
1353
|
+
}
|
|
1354
|
+
get selectedColumnsCount() {
|
|
1355
|
+
return this.columns.filter(col => col.selected).length;
|
|
1356
|
+
}
|
|
1357
|
+
get canExport() {
|
|
1358
|
+
return this.selectedColumnsCount > 0 && this.recordsToExport.length > 0;
|
|
1359
|
+
}
|
|
1360
|
+
onHide() {
|
|
1361
|
+
this.visibleChange.emit(false);
|
|
1362
|
+
}
|
|
1363
|
+
selectAllColumns() {
|
|
1364
|
+
this.columns.forEach(col => col.selected = true);
|
|
1365
|
+
}
|
|
1366
|
+
deselectAllColumns() {
|
|
1367
|
+
this.columns.forEach(col => col.selected = false);
|
|
1368
|
+
}
|
|
1369
|
+
export() {
|
|
1370
|
+
if (!this.canExport)
|
|
1371
|
+
return;
|
|
1372
|
+
if (this.exportFormat === 'xlsx') {
|
|
1373
|
+
this.exportToExcel();
|
|
1374
|
+
}
|
|
1375
|
+
else {
|
|
1376
|
+
this.exportToPDF();
|
|
1377
|
+
}
|
|
1378
|
+
this.onHide();
|
|
1379
|
+
}
|
|
1380
|
+
exportToExcel() {
|
|
1381
|
+
const selectedColumns = this.columns.filter(col => col.selected);
|
|
1382
|
+
const data = this.recordsToExport.map(record => {
|
|
1383
|
+
const row = {};
|
|
1384
|
+
selectedColumns.forEach(col => {
|
|
1385
|
+
if (col.field === 'status' && record.status && this.statusLabels) {
|
|
1386
|
+
row[col.header] = this.statusLabels[record.status];
|
|
1387
|
+
}
|
|
1388
|
+
else {
|
|
1389
|
+
row[col.header] = record[col.field];
|
|
1390
|
+
}
|
|
1391
|
+
});
|
|
1392
|
+
return row;
|
|
1393
|
+
});
|
|
1394
|
+
const worksheet = XLSX.utils.json_to_sheet(data);
|
|
1395
|
+
const workbook = XLSX.utils.book_new();
|
|
1396
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, this.t('export_data_sheet_name'));
|
|
1397
|
+
// Ajustar largura das colunas
|
|
1398
|
+
const maxWidth = 30;
|
|
1399
|
+
const colWidths = selectedColumns.map(col => ({
|
|
1400
|
+
wch: Math.min(col.header.length + 5, maxWidth)
|
|
1401
|
+
}));
|
|
1402
|
+
worksheet['!cols'] = colWidths;
|
|
1403
|
+
const fileName = `export-${new Date().getTime()}.xlsx`;
|
|
1404
|
+
XLSX.writeFile(workbook, fileName);
|
|
1405
|
+
}
|
|
1406
|
+
exportToPDF() {
|
|
1407
|
+
const doc = new jsPDF();
|
|
1408
|
+
const selectedColumns = this.columns.filter(col => col.selected);
|
|
1409
|
+
// Título
|
|
1410
|
+
doc.setFontSize(18);
|
|
1411
|
+
doc.setTextColor(18, 168, 133);
|
|
1412
|
+
doc.text(this.t('export_pdf_title'), 14, 20);
|
|
1413
|
+
// Subtítulo
|
|
1414
|
+
doc.setFontSize(10);
|
|
1415
|
+
doc.setTextColor(100);
|
|
1416
|
+
doc.text(`${this.t('export_exported_at')}: ${new Date().toLocaleString('pt-BR')}`, 14, 28);
|
|
1417
|
+
doc.text(`${this.t('export_total_records')}: ${this.recordsToExport.length}`, 14, 34);
|
|
1418
|
+
// Preparar dados
|
|
1419
|
+
const headers = selectedColumns.map(col => col.header);
|
|
1420
|
+
const data = this.recordsToExport.map(record => {
|
|
1421
|
+
return selectedColumns.map(col => {
|
|
1422
|
+
if (col.field === 'status' && record.status && this.statusLabels) {
|
|
1423
|
+
return this.statusLabels[record.status];
|
|
1424
|
+
}
|
|
1425
|
+
return String(record[col.field] || '');
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
// Tabela
|
|
1429
|
+
autoTable(doc, {
|
|
1430
|
+
head: [headers],
|
|
1431
|
+
body: data,
|
|
1432
|
+
startY: 40,
|
|
1433
|
+
theme: 'grid',
|
|
1434
|
+
headStyles: {
|
|
1435
|
+
fillColor: [18, 168, 133],
|
|
1436
|
+
textColor: [255, 255, 255],
|
|
1437
|
+
fontStyle: 'bold',
|
|
1438
|
+
halign: 'center'
|
|
1439
|
+
},
|
|
1440
|
+
styles: {
|
|
1441
|
+
fontSize: 9,
|
|
1442
|
+
cellPadding: 3
|
|
1443
|
+
},
|
|
1444
|
+
alternateRowStyles: {
|
|
1445
|
+
fillColor: [248, 255, 254]
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
const fileName = `export-${new Date().getTime()}.pdf`;
|
|
1449
|
+
doc.save(fileName);
|
|
1450
|
+
}
|
|
1451
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ExportDialogComponent, deps: [{ token: 'TranslationService', optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
1452
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ExportDialogComponent, isStandalone: true, selector: "sia-export-dialog", inputs: { visible: "visible", allRecords: "allRecords", selectedRecords: "selectedRecords", statusLabels: "statusLabels", translationPrefix: "translationPrefix", availableColumns: "availableColumns" }, outputs: { visibleChange: "visibleChange" }, usesOnChanges: true, ngImport: i0, template: "<p-dialog\n [(visible)]=\"visible\"\n (onHide)=\"onHide()\"\n [modal]=\"true\"\n [draggable]=\"false\"\n [resizable]=\"false\"\n [style]=\"{ width: '700px' }\"\n styleClass=\"export-dialog\"\n>\n <ng-template pTemplate=\"header\">\n <div class=\"custom-header\">\n <i class=\"pi pi-download header-icon\"></i>\n <span>{{ t('export_data_title') }}</span>\n </div>\n </ng-template>\n\n <div class=\"export-content\">\n <!-- Formato de Exporta\u00E7\u00E3o -->\n <div class=\"export-section\">\n <div class=\"section-header\">\n <div class=\"section-icon\">\n <i class=\"pi pi-file\"></i>\n </div>\n <h3>{{ t('export_format_title') }}</h3>\n </div>\n <div class=\"format-options\">\n <div\n *ngFor=\"let format of formats\"\n class=\"format-card\"\n [class.selected]=\"exportFormat === format.value\"\n (click)=\"exportFormat = format.value\"\n >\n <div class=\"card-radio\">\n <p-radioButton\n [value]=\"format.value\"\n [(ngModel)]=\"exportFormat\"\n [inputId]=\"format.value\"\n ></p-radioButton>\n </div>\n <div class=\"card-icon\" [class.excel]=\"format.value === 'xlsx'\" [class.pdf]=\"format.value === 'pdf'\">\n <i class=\"pi\" [ngClass]=\"format.icon\"></i>\n </div>\n <div class=\"card-info\">\n <label [for]=\"format.value\" class=\"card-label\">{{ format.label }}</label>\n <span class=\"card-description\">{{ format.description }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Escopo de Exporta\u00E7\u00E3o -->\n <div class=\"export-section\">\n <div class=\"section-header\">\n <div class=\"section-icon\">\n <i class=\"pi pi-filter\"></i>\n </div>\n <h3>{{ t('export_scope_title') }}</h3>\n </div>\n <div class=\"scope-options\">\n <div\n *ngFor=\"let scope of scopes\"\n class=\"scope-card\"\n [class.selected]=\"exportScope === scope.value\"\n [class.disabled]=\"scope.value === 'selected' && selectedRecords.length === 0\"\n (click)=\"scope.value === 'selected' && selectedRecords.length === 0 ? null : exportScope = scope.value\"\n >\n <div class=\"card-radio\">\n <p-radioButton\n [value]=\"scope.value\"\n [(ngModel)]=\"exportScope\"\n [inputId]=\"scope.value\"\n [disabled]=\"scope.value === 'selected' && selectedRecords.length === 0\"\n ></p-radioButton>\n </div>\n <div class=\"card-icon\">\n <i class=\"pi\" [ngClass]=\"scope.icon\"></i>\n </div>\n <div class=\"card-info\">\n <label [for]=\"scope.value\" class=\"card-label\">{{ scope.label }}</label>\n <span class=\"card-description\">{{ scope.description }}</span>\n <span class=\"card-count\">\n <i class=\"pi pi-circle-fill\"></i>\n {{ scope.value === 'all' ? allRecords.length : selectedRecords.length }} {{ t('export_records_count') }}\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Sele\u00E7\u00E3o de Colunas -->\n <div class=\"export-section\">\n <div class=\"section-header\">\n <div class=\"section-icon\">\n <i class=\"pi pi-table\"></i>\n </div>\n <h3>{{ t('export_columns_title') }}</h3>\n <div class=\"column-actions\">\n <button type=\"button\" class=\"action-link\" (click)=\"selectAllColumns()\">\n {{ t('export_select_all_columns') }}\n </button>\n <span class=\"separator\">|</span>\n <button type=\"button\" class=\"action-link\" (click)=\"deselectAllColumns()\">\n {{ t('export_clear_selection') }}\n </button>\n </div>\n </div>\n <div class=\"columns-grid\">\n <div\n *ngFor=\"let column of columns\"\n class=\"column-item\"\n [class.selected]=\"column.selected\"\n (click)=\"column.selected = !column.selected\"\n >\n <p-checkbox\n [(ngModel)]=\"column.selected\"\n [binary]=\"true\"\n [inputId]=\"column.field\"\n ></p-checkbox>\n <label [for]=\"column.field\" class=\"column-label\">\n {{ column.header }}\n </label>\n </div>\n </div>\n <div class=\"columns-summary\">\n <i class=\"pi pi-info-circle\"></i>\n <span>{{ selectedColumnsCount }} {{ t('export_columns_summary') }} {{ columns.length }} {{ t('export_columns_selected') }}</span>\n </div>\n </div>\n </div>\n\n <ng-template pTemplate=\"footer\">\n <p-button\n [label]=\"t('cancel')\"\n icon=\"pi pi-times\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"onHide()\"\n ></p-button>\n <p-button\n [label]=\"t('export_button')\"\n icon=\"pi pi-download\"\n [disabled]=\"!canExport\"\n (onClick)=\"export()\"\n ></p-button>\n </ng-template>\n</p-dialog>\n", styles: ["::ng-deep .export-dialog .p-dialog{border-radius:16px;overflow:hidden;box-shadow:0 20px 60px #0000004d}::ng-deep .export-dialog .p-dialog-content{padding:0;background:#fff;max-height:70vh;overflow-y:auto}.custom-header{display:flex;align-items:center;gap:12px;font-size:18px;font-weight:600}.custom-header .header-icon{font-size:22px;color:var(--p-primary-500)}.export-content{padding:28px 32px}.export-section{margin-bottom:32px}.export-section:last-child{margin-bottom:0}.export-section .section-header{display:flex;align-items:center;gap:12px;margin-bottom:20px}.export-section .section-header .section-icon{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.1),rgba(var(--p-primary-rgb),.15));border-radius:10px}.export-section .section-header .section-icon i{font-size:18px;color:var(--p-primary-500)}.export-section .section-header h3{margin:0;font-size:17px;font-weight:700;color:var(--neutral-color-800);flex:1}.export-section .section-header .column-actions{display:flex;align-items:center;gap:10px;font-size:13px}.export-section .section-header .column-actions .action-link{background:none;border:none;color:var(--p-primary-500);cursor:pointer;font-weight:600;padding:0;transition:all .2s;font-size:13px}.export-section .section-header .column-actions .action-link:hover{color:var(--p-primary-500)}.export-section .section-header .column-actions .separator{color:var(--neutral-color-300);font-weight:400}.format-options{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}.format-card{display:flex;align-items:center;gap:16px;padding:20px;border:2px solid #e5e7eb;border-radius:12px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);background:#fff;position:relative;overflow:hidden}.format-card:before{content:\"\";position:absolute;inset:0;background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.03),rgba(var(--p-primary-rgb),.06));opacity:0;transition:opacity .3s}.format-card:hover{border-color:var(--p-primary-500);transform:translateY(-2px);box-shadow:0 8px 24px rgba(var(--p-primary-rgb),.15)}.format-card:hover:before{opacity:1}.format-card.selected{border-color:var(--p-primary-500);background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.05),rgba(var(--p-primary-rgb),.08));box-shadow:0 0 0 4px rgba(var(--p-primary-rgb),.1)}.format-card.selected .card-icon{background:linear-gradient(135deg,var(--p-primary-500) 0%,var(--p-primary-500) 100%);transform:scale(1.05)}.format-card.selected .card-icon i{color:#fff}.format-card .card-radio ::ng-deep .p-radiobutton .p-radiobutton-box{width:20px;height:20px;border-width:2px}.format-card .card-icon{width:52px;height:52px;display:flex;align-items:center;justify-content:center;border-radius:12px;transition:all .3s;position:relative;z-index:1}.format-card .card-icon.excel{background:linear-gradient(135deg,var(--p-primary-50) 0%,var(--p-primary-100) 100%)}.format-card .card-icon.excel i{color:#107c41;font-size:26px}.format-card .card-icon.pdf{background:linear-gradient(135deg,#fee8e8,#fdd4d4)}.format-card .card-icon.pdf i{color:#dc2626;font-size:26px}.format-card .card-info{flex:1;display:flex;flex-direction:column;gap:4px;position:relative;z-index:1}.format-card .card-label{font-size:16px;font-weight:700;color:var(--neutral-color-800);cursor:pointer;margin:0}.format-card .card-description{font-size:13px;color:var(--neutral-color-600);line-height:1.4}.scope-options{display:flex;flex-direction:column;gap:14px}.scope-card{display:flex;align-items:center;gap:16px;padding:20px;border:2px solid #e5e7eb;border-radius:12px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);background:#fff;position:relative;overflow:hidden}.scope-card:before{content:\"\";position:absolute;inset:0;background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.03),rgba(var(--p-primary-rgb),.06));opacity:0;transition:opacity .3s}.scope-card:hover:not(.disabled){border-color:var(--p-primary-500);box-shadow:0 4px 16px rgba(var(--p-primary-rgb),.12)}.scope-card:hover:not(.disabled):before{opacity:1}.scope-card.selected{border-color:var(--p-primary-500);background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.05),rgba(var(--p-primary-rgb),.08));box-shadow:0 0 0 4px rgba(var(--p-primary-rgb),.1)}.scope-card.selected .card-icon{background:linear-gradient(135deg,var(--p-primary-500) 0%,var(--p-primary-500) 100%);transform:scale(1.05)}.scope-card.selected .card-icon i{color:#fff}.scope-card.disabled{opacity:.5;cursor:not-allowed;background:#f9fafb}.scope-card.disabled:hover{border-color:#e5e7eb;box-shadow:none}.scope-card .card-radio ::ng-deep .p-radiobutton .p-radiobutton-box{width:20px;height:20px;border-width:2px}.scope-card .card-icon{width:48px;height:48px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--p-primary-50) 0%,var(--p-primary-100) 100%);border-radius:12px;transition:all .3s;position:relative;z-index:1}.scope-card .card-icon i{font-size:22px;color:var(--p-primary-500);transition:all .3s}.scope-card .card-info{flex:1;display:flex;flex-direction:column;gap:4px;position:relative;z-index:1}.scope-card .card-label{font-size:16px;font-weight:700;color:var(--neutral-color-800);cursor:pointer;margin:0}.scope-card .card-description{font-size:13px;color:var(--neutral-color-600);line-height:1.4}.scope-card .card-count{font-size:13px;font-weight:600;color:var(--p-primary-500);margin-top:6px;display:inline-flex;align-items:center;gap:6px}.scope-card .card-count i{font-size:6px}.columns-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:16px}.column-item{display:flex;align-items:center;gap:12px;padding:16px;border:2px solid #e5e7eb;border-radius:10px;transition:all .2s;background:#fff;cursor:pointer}.column-item:hover{border-color:var(--p-primary-500);background:rgba(var(--p-primary-rgb),.03)}.column-item.selected{border-color:var(--p-primary-500);background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.05),rgba(var(--p-primary-rgb),.08))}.column-item ::ng-deep .p-checkbox .p-checkbox-box{width:20px;height:20px;border-width:2px}.column-item .column-label{font-size:14px;font-weight:600;color:var(--neutral-color-800);cursor:pointer;margin:0;-webkit-user-select:none;user-select:none}.columns-summary{display:flex;align-items:center;gap:10px;padding:14px 18px;background:linear-gradient(135deg,var(--p-primary-50) 0%,var(--p-primary-100) 100%);border-radius:10px;font-size:14px;color:var(--neutral-color-700);font-weight:600}.columns-summary i{color:var(--p-primary-500);font-size:18px}@media (max-width: 768px){::ng-deep .export-dialog .p-dialog{width:95vw!important;margin:0}.dialog-header{padding:28px 24px}.dialog-header .header-icon-wrapper{width:56px;height:56px}.dialog-header .header-icon-wrapper i{font-size:28px}.dialog-header .header-text h2{font-size:24px}.export-content{padding:24px}.format-options,.columns-grid{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i5.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i5$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: RadioButtonModule }, { kind: "component", type: i6.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"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i7.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"] }, { kind: "ngmodule", type: DividerModule }] });
|
|
1453
|
+
}
|
|
1454
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ExportDialogComponent, decorators: [{
|
|
1455
|
+
type: Component,
|
|
1456
|
+
args: [{ selector: 'sia-export-dialog', standalone: true, imports: [
|
|
1457
|
+
CommonModule,
|
|
1458
|
+
FormsModule,
|
|
1459
|
+
DialogModule,
|
|
1460
|
+
ButtonModule,
|
|
1461
|
+
RadioButtonModule,
|
|
1462
|
+
CheckboxModule,
|
|
1463
|
+
DividerModule
|
|
1464
|
+
], template: "<p-dialog\n [(visible)]=\"visible\"\n (onHide)=\"onHide()\"\n [modal]=\"true\"\n [draggable]=\"false\"\n [resizable]=\"false\"\n [style]=\"{ width: '700px' }\"\n styleClass=\"export-dialog\"\n>\n <ng-template pTemplate=\"header\">\n <div class=\"custom-header\">\n <i class=\"pi pi-download header-icon\"></i>\n <span>{{ t('export_data_title') }}</span>\n </div>\n </ng-template>\n\n <div class=\"export-content\">\n <!-- Formato de Exporta\u00E7\u00E3o -->\n <div class=\"export-section\">\n <div class=\"section-header\">\n <div class=\"section-icon\">\n <i class=\"pi pi-file\"></i>\n </div>\n <h3>{{ t('export_format_title') }}</h3>\n </div>\n <div class=\"format-options\">\n <div\n *ngFor=\"let format of formats\"\n class=\"format-card\"\n [class.selected]=\"exportFormat === format.value\"\n (click)=\"exportFormat = format.value\"\n >\n <div class=\"card-radio\">\n <p-radioButton\n [value]=\"format.value\"\n [(ngModel)]=\"exportFormat\"\n [inputId]=\"format.value\"\n ></p-radioButton>\n </div>\n <div class=\"card-icon\" [class.excel]=\"format.value === 'xlsx'\" [class.pdf]=\"format.value === 'pdf'\">\n <i class=\"pi\" [ngClass]=\"format.icon\"></i>\n </div>\n <div class=\"card-info\">\n <label [for]=\"format.value\" class=\"card-label\">{{ format.label }}</label>\n <span class=\"card-description\">{{ format.description }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Escopo de Exporta\u00E7\u00E3o -->\n <div class=\"export-section\">\n <div class=\"section-header\">\n <div class=\"section-icon\">\n <i class=\"pi pi-filter\"></i>\n </div>\n <h3>{{ t('export_scope_title') }}</h3>\n </div>\n <div class=\"scope-options\">\n <div\n *ngFor=\"let scope of scopes\"\n class=\"scope-card\"\n [class.selected]=\"exportScope === scope.value\"\n [class.disabled]=\"scope.value === 'selected' && selectedRecords.length === 0\"\n (click)=\"scope.value === 'selected' && selectedRecords.length === 0 ? null : exportScope = scope.value\"\n >\n <div class=\"card-radio\">\n <p-radioButton\n [value]=\"scope.value\"\n [(ngModel)]=\"exportScope\"\n [inputId]=\"scope.value\"\n [disabled]=\"scope.value === 'selected' && selectedRecords.length === 0\"\n ></p-radioButton>\n </div>\n <div class=\"card-icon\">\n <i class=\"pi\" [ngClass]=\"scope.icon\"></i>\n </div>\n <div class=\"card-info\">\n <label [for]=\"scope.value\" class=\"card-label\">{{ scope.label }}</label>\n <span class=\"card-description\">{{ scope.description }}</span>\n <span class=\"card-count\">\n <i class=\"pi pi-circle-fill\"></i>\n {{ scope.value === 'all' ? allRecords.length : selectedRecords.length }} {{ t('export_records_count') }}\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Sele\u00E7\u00E3o de Colunas -->\n <div class=\"export-section\">\n <div class=\"section-header\">\n <div class=\"section-icon\">\n <i class=\"pi pi-table\"></i>\n </div>\n <h3>{{ t('export_columns_title') }}</h3>\n <div class=\"column-actions\">\n <button type=\"button\" class=\"action-link\" (click)=\"selectAllColumns()\">\n {{ t('export_select_all_columns') }}\n </button>\n <span class=\"separator\">|</span>\n <button type=\"button\" class=\"action-link\" (click)=\"deselectAllColumns()\">\n {{ t('export_clear_selection') }}\n </button>\n </div>\n </div>\n <div class=\"columns-grid\">\n <div\n *ngFor=\"let column of columns\"\n class=\"column-item\"\n [class.selected]=\"column.selected\"\n (click)=\"column.selected = !column.selected\"\n >\n <p-checkbox\n [(ngModel)]=\"column.selected\"\n [binary]=\"true\"\n [inputId]=\"column.field\"\n ></p-checkbox>\n <label [for]=\"column.field\" class=\"column-label\">\n {{ column.header }}\n </label>\n </div>\n </div>\n <div class=\"columns-summary\">\n <i class=\"pi pi-info-circle\"></i>\n <span>{{ selectedColumnsCount }} {{ t('export_columns_summary') }} {{ columns.length }} {{ t('export_columns_selected') }}</span>\n </div>\n </div>\n </div>\n\n <ng-template pTemplate=\"footer\">\n <p-button\n [label]=\"t('cancel')\"\n icon=\"pi pi-times\"\n severity=\"secondary\"\n [text]=\"true\"\n (onClick)=\"onHide()\"\n ></p-button>\n <p-button\n [label]=\"t('export_button')\"\n icon=\"pi pi-download\"\n [disabled]=\"!canExport\"\n (onClick)=\"export()\"\n ></p-button>\n </ng-template>\n</p-dialog>\n", styles: ["::ng-deep .export-dialog .p-dialog{border-radius:16px;overflow:hidden;box-shadow:0 20px 60px #0000004d}::ng-deep .export-dialog .p-dialog-content{padding:0;background:#fff;max-height:70vh;overflow-y:auto}.custom-header{display:flex;align-items:center;gap:12px;font-size:18px;font-weight:600}.custom-header .header-icon{font-size:22px;color:var(--p-primary-500)}.export-content{padding:28px 32px}.export-section{margin-bottom:32px}.export-section:last-child{margin-bottom:0}.export-section .section-header{display:flex;align-items:center;gap:12px;margin-bottom:20px}.export-section .section-header .section-icon{width:36px;height:36px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.1),rgba(var(--p-primary-rgb),.15));border-radius:10px}.export-section .section-header .section-icon i{font-size:18px;color:var(--p-primary-500)}.export-section .section-header h3{margin:0;font-size:17px;font-weight:700;color:var(--neutral-color-800);flex:1}.export-section .section-header .column-actions{display:flex;align-items:center;gap:10px;font-size:13px}.export-section .section-header .column-actions .action-link{background:none;border:none;color:var(--p-primary-500);cursor:pointer;font-weight:600;padding:0;transition:all .2s;font-size:13px}.export-section .section-header .column-actions .action-link:hover{color:var(--p-primary-500)}.export-section .section-header .column-actions .separator{color:var(--neutral-color-300);font-weight:400}.format-options{display:grid;grid-template-columns:repeat(2,1fr);gap:16px}.format-card{display:flex;align-items:center;gap:16px;padding:20px;border:2px solid #e5e7eb;border-radius:12px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);background:#fff;position:relative;overflow:hidden}.format-card:before{content:\"\";position:absolute;inset:0;background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.03),rgba(var(--p-primary-rgb),.06));opacity:0;transition:opacity .3s}.format-card:hover{border-color:var(--p-primary-500);transform:translateY(-2px);box-shadow:0 8px 24px rgba(var(--p-primary-rgb),.15)}.format-card:hover:before{opacity:1}.format-card.selected{border-color:var(--p-primary-500);background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.05),rgba(var(--p-primary-rgb),.08));box-shadow:0 0 0 4px rgba(var(--p-primary-rgb),.1)}.format-card.selected .card-icon{background:linear-gradient(135deg,var(--p-primary-500) 0%,var(--p-primary-500) 100%);transform:scale(1.05)}.format-card.selected .card-icon i{color:#fff}.format-card .card-radio ::ng-deep .p-radiobutton .p-radiobutton-box{width:20px;height:20px;border-width:2px}.format-card .card-icon{width:52px;height:52px;display:flex;align-items:center;justify-content:center;border-radius:12px;transition:all .3s;position:relative;z-index:1}.format-card .card-icon.excel{background:linear-gradient(135deg,var(--p-primary-50) 0%,var(--p-primary-100) 100%)}.format-card .card-icon.excel i{color:#107c41;font-size:26px}.format-card .card-icon.pdf{background:linear-gradient(135deg,#fee8e8,#fdd4d4)}.format-card .card-icon.pdf i{color:#dc2626;font-size:26px}.format-card .card-info{flex:1;display:flex;flex-direction:column;gap:4px;position:relative;z-index:1}.format-card .card-label{font-size:16px;font-weight:700;color:var(--neutral-color-800);cursor:pointer;margin:0}.format-card .card-description{font-size:13px;color:var(--neutral-color-600);line-height:1.4}.scope-options{display:flex;flex-direction:column;gap:14px}.scope-card{display:flex;align-items:center;gap:16px;padding:20px;border:2px solid #e5e7eb;border-radius:12px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);background:#fff;position:relative;overflow:hidden}.scope-card:before{content:\"\";position:absolute;inset:0;background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.03),rgba(var(--p-primary-rgb),.06));opacity:0;transition:opacity .3s}.scope-card:hover:not(.disabled){border-color:var(--p-primary-500);box-shadow:0 4px 16px rgba(var(--p-primary-rgb),.12)}.scope-card:hover:not(.disabled):before{opacity:1}.scope-card.selected{border-color:var(--p-primary-500);background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.05),rgba(var(--p-primary-rgb),.08));box-shadow:0 0 0 4px rgba(var(--p-primary-rgb),.1)}.scope-card.selected .card-icon{background:linear-gradient(135deg,var(--p-primary-500) 0%,var(--p-primary-500) 100%);transform:scale(1.05)}.scope-card.selected .card-icon i{color:#fff}.scope-card.disabled{opacity:.5;cursor:not-allowed;background:#f9fafb}.scope-card.disabled:hover{border-color:#e5e7eb;box-shadow:none}.scope-card .card-radio ::ng-deep .p-radiobutton .p-radiobutton-box{width:20px;height:20px;border-width:2px}.scope-card .card-icon{width:48px;height:48px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--p-primary-50) 0%,var(--p-primary-100) 100%);border-radius:12px;transition:all .3s;position:relative;z-index:1}.scope-card .card-icon i{font-size:22px;color:var(--p-primary-500);transition:all .3s}.scope-card .card-info{flex:1;display:flex;flex-direction:column;gap:4px;position:relative;z-index:1}.scope-card .card-label{font-size:16px;font-weight:700;color:var(--neutral-color-800);cursor:pointer;margin:0}.scope-card .card-description{font-size:13px;color:var(--neutral-color-600);line-height:1.4}.scope-card .card-count{font-size:13px;font-weight:600;color:var(--p-primary-500);margin-top:6px;display:inline-flex;align-items:center;gap:6px}.scope-card .card-count i{font-size:6px}.columns-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:16px}.column-item{display:flex;align-items:center;gap:12px;padding:16px;border:2px solid #e5e7eb;border-radius:10px;transition:all .2s;background:#fff;cursor:pointer}.column-item:hover{border-color:var(--p-primary-500);background:rgba(var(--p-primary-rgb),.03)}.column-item.selected{border-color:var(--p-primary-500);background:linear-gradient(135deg,rgba(var(--p-primary-rgb),.05),rgba(var(--p-primary-rgb),.08))}.column-item ::ng-deep .p-checkbox .p-checkbox-box{width:20px;height:20px;border-width:2px}.column-item .column-label{font-size:14px;font-weight:600;color:var(--neutral-color-800);cursor:pointer;margin:0;-webkit-user-select:none;user-select:none}.columns-summary{display:flex;align-items:center;gap:10px;padding:14px 18px;background:linear-gradient(135deg,var(--p-primary-50) 0%,var(--p-primary-100) 100%);border-radius:10px;font-size:14px;color:var(--neutral-color-700);font-weight:600}.columns-summary i{color:var(--p-primary-500);font-size:18px}@media (max-width: 768px){::ng-deep .export-dialog .p-dialog{width:95vw!important;margin:0}.dialog-header{padding:28px 24px}.dialog-header .header-icon-wrapper{width:56px;height:56px}.dialog-header .header-icon-wrapper i{font-size:28px}.dialog-header .header-text h2{font-size:24px}.export-content{padding:24px}.format-options,.columns-grid{grid-template-columns:1fr}}\n"] }]
|
|
1465
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1466
|
+
type: Optional
|
|
1467
|
+
}, {
|
|
1468
|
+
type: Inject,
|
|
1469
|
+
args: ['TranslationService']
|
|
1470
|
+
}] }], propDecorators: { visible: [{
|
|
1471
|
+
type: Input
|
|
1472
|
+
}], allRecords: [{
|
|
1473
|
+
type: Input
|
|
1474
|
+
}], selectedRecords: [{
|
|
1475
|
+
type: Input
|
|
1476
|
+
}], statusLabels: [{
|
|
1477
|
+
type: Input
|
|
1478
|
+
}], translationPrefix: [{
|
|
1479
|
+
type: Input
|
|
1480
|
+
}], availableColumns: [{
|
|
1481
|
+
type: Input
|
|
1482
|
+
}], visibleChange: [{
|
|
1483
|
+
type: Output
|
|
1484
|
+
}] } });
|
|
1485
|
+
|
|
1486
|
+
class BulkDeleteDialogComponent {
|
|
1487
|
+
translationService;
|
|
1488
|
+
visible = false;
|
|
1489
|
+
stages = []; // Generic entities
|
|
1490
|
+
service; // Generic service
|
|
1491
|
+
entityNameField = 'name'; // Field to display entity name
|
|
1492
|
+
entityCodeField = 'code'; // Field to display entity code (optional)
|
|
1493
|
+
translationPrefix = 'crmx.business';
|
|
1494
|
+
visibleChange = new EventEmitter();
|
|
1495
|
+
onComplete = new EventEmitter();
|
|
1496
|
+
deleteStatuses = [];
|
|
1497
|
+
isProcessing = false;
|
|
1498
|
+
isCompleted = false;
|
|
1499
|
+
currentIndex = 0;
|
|
1500
|
+
successCount = 0;
|
|
1501
|
+
errorCount = 0;
|
|
1502
|
+
constructor(translationService) {
|
|
1503
|
+
this.translationService = translationService;
|
|
1504
|
+
}
|
|
1505
|
+
t(key, params) {
|
|
1506
|
+
if (this.translationService) {
|
|
1507
|
+
const fullKey = `${this.translationPrefix}.${key}`;
|
|
1508
|
+
const translated = this.translationService.translate(fullKey, params);
|
|
1509
|
+
// Se params foi fornecido e a tradução contém placeholders, fazer replace manual
|
|
1510
|
+
if (params && translated.includes('{{')) {
|
|
1511
|
+
return Object.keys(params).reduce((text, key) => {
|
|
1512
|
+
return text.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), params[key]);
|
|
1513
|
+
}, translated);
|
|
1514
|
+
}
|
|
1515
|
+
return translated;
|
|
1516
|
+
}
|
|
1517
|
+
return key;
|
|
1518
|
+
}
|
|
1519
|
+
ngOnChanges() {
|
|
1520
|
+
if (this.visible && this.stages.length > 0) {
|
|
1521
|
+
this.initializeStatuses();
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
initializeStatuses() {
|
|
1525
|
+
this.deleteStatuses = this.stages.map(entity => ({
|
|
1526
|
+
entity,
|
|
1527
|
+
status: 'pending'
|
|
1528
|
+
}));
|
|
1529
|
+
this.isProcessing = false;
|
|
1530
|
+
this.isCompleted = false;
|
|
1531
|
+
this.currentIndex = 0;
|
|
1532
|
+
this.successCount = 0;
|
|
1533
|
+
this.errorCount = 0;
|
|
1534
|
+
}
|
|
1535
|
+
get progress() {
|
|
1536
|
+
if (this.deleteStatuses.length === 0)
|
|
1537
|
+
return 0;
|
|
1538
|
+
return Math.round((this.currentIndex / this.deleteStatuses.length) * 100);
|
|
1539
|
+
}
|
|
1540
|
+
get canClose() {
|
|
1541
|
+
return !this.isProcessing || this.isCompleted;
|
|
1542
|
+
}
|
|
1543
|
+
startDeletion() {
|
|
1544
|
+
if (!this.service) {
|
|
1545
|
+
console.error('Service not provided to BulkDeleteDialogComponent');
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
this.isProcessing = true;
|
|
1549
|
+
this.isCompleted = false;
|
|
1550
|
+
// Process deletions sequentially
|
|
1551
|
+
from(this.deleteStatuses)
|
|
1552
|
+
.pipe(concatMap((deleteStatus, index) => {
|
|
1553
|
+
this.currentIndex = index;
|
|
1554
|
+
deleteStatus.status = 'processing';
|
|
1555
|
+
return this.service.delete(deleteStatus.entity.id).pipe(concatMap(() => {
|
|
1556
|
+
deleteStatus.status = 'success';
|
|
1557
|
+
this.successCount++;
|
|
1558
|
+
return of(null);
|
|
1559
|
+
}), catchError$1((error) => {
|
|
1560
|
+
// Check if it's actually a success (204 No Content)
|
|
1561
|
+
if (error?.status === 204) {
|
|
1562
|
+
deleteStatus.status = 'success';
|
|
1563
|
+
this.successCount++;
|
|
1564
|
+
}
|
|
1565
|
+
else {
|
|
1566
|
+
deleteStatus.status = 'error';
|
|
1567
|
+
deleteStatus.errorMessage = error?.message || error?.statusText || this.t('bulk_delete_error_generic');
|
|
1568
|
+
this.errorCount++;
|
|
1569
|
+
}
|
|
1570
|
+
return of(null);
|
|
1571
|
+
}));
|
|
1572
|
+
}))
|
|
1573
|
+
.subscribe({
|
|
1574
|
+
complete: () => {
|
|
1575
|
+
this.currentIndex = this.deleteStatuses.length;
|
|
1576
|
+
this.isCompleted = true;
|
|
1577
|
+
this.isProcessing = false;
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
onHide() {
|
|
1582
|
+
if (!this.canClose)
|
|
1583
|
+
return;
|
|
1584
|
+
this.visible = false;
|
|
1585
|
+
this.visibleChange.emit(false);
|
|
1586
|
+
if (this.isCompleted && this.successCount > 0) {
|
|
1587
|
+
this.onComplete.emit();
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
onClose() {
|
|
1591
|
+
this.onHide();
|
|
1592
|
+
}
|
|
1593
|
+
getStatusSeverity(status) {
|
|
1594
|
+
switch (status) {
|
|
1595
|
+
case 'pending': return 'secondary';
|
|
1596
|
+
case 'processing': return 'info';
|
|
1597
|
+
case 'success': return 'success';
|
|
1598
|
+
case 'error': return 'danger';
|
|
1599
|
+
default: return 'secondary';
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
getStatusLabel(status) {
|
|
1603
|
+
return this.t(`bulk_delete_status_${status}`);
|
|
1604
|
+
}
|
|
1605
|
+
getStatusIcon(status) {
|
|
1606
|
+
switch (status) {
|
|
1607
|
+
case 'pending': return 'pi pi-clock';
|
|
1608
|
+
case 'processing': return 'pi pi-spin pi-spinner';
|
|
1609
|
+
case 'success': return 'pi pi-check';
|
|
1610
|
+
case 'error': return 'pi pi-times';
|
|
1611
|
+
default: return 'pi pi-question';
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BulkDeleteDialogComponent, deps: [{ token: 'TranslationService', optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
1615
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: BulkDeleteDialogComponent, isStandalone: true, selector: "sia-bulk-delete-dialog", inputs: { visible: "visible", stages: "stages", service: "service", entityNameField: "entityNameField", entityCodeField: "entityCodeField", translationPrefix: "translationPrefix" }, outputs: { visibleChange: "visibleChange", onComplete: "onComplete" }, usesOnChanges: true, ngImport: i0, template: "<p-dialog\n [(visible)]=\"visible\"\n [modal]=\"true\"\n [closable]=\"canClose\"\n [closeOnEscape]=\"canClose\"\n [dismissableMask]=\"false\"\n [style]=\"{ width: '700px', maxWidth: '90vw' }\"\n (onHide)=\"onHide()\"\n>\n <ng-template pTemplate=\"header\">\n <div class=\"dialog-header\">\n <i class=\"pi pi-trash header-icon\"></i>\n <h2>{{ t('bulk_delete_title') }}</h2>\n </div>\n </ng-template>\n\n <div class=\"dialog-content\">\n <!-- Summary -->\n @if (!isProcessing && !isCompleted) {\n <div class=\"summary-section\">\n <p class=\"summary-text\">\n <i class=\"pi pi-exclamation-triangle warning-icon\"></i>\n <span [innerHTML]=\"t('bulk_delete_warning', { count: stages.length })\"></span>\n </p>\n <p class=\"summary-description\">\n {{ t('bulk_delete_description') }}\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (isProcessing || isCompleted) {\n <div class=\"progress-section\">\n <div class=\"progress-info\">\n <span class=\"progress-label\">{{ t('bulk_delete_progress') }}</span>\n <span class=\"progress-count\">{{ currentIndex }} / {{ deleteStatuses.length }}</span>\n </div>\n <p-progressBar \n [value]=\"progress\" \n [showValue]=\"false\"\n styleClass=\"custom-progress\"\n ></p-progressBar>\n \n <div class=\"progress-stats\">\n <div class=\"stat success\">\n <i class=\"pi pi-check-circle\"></i>\n <span>{{ successCount }} {{ t('bulk_delete_success_count') }}</span>\n </div>\n @if (errorCount > 0) {\n <div class=\"stat error\">\n <i class=\"pi pi-times-circle\"></i>\n <span>{{ errorCount }} {{ t('bulk_delete_error_count') }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Status List -->\n @if (isProcessing || isCompleted) {\n <div class=\"status-list\">\n @for (item of deleteStatuses; track item.entity) {\n <div class=\"status-item\">\n <div class=\"status-info\">\n <i [class]=\"getStatusIcon(item.status)\" class=\"status-icon\"></i>\n <div class=\"status-details\">\n <span class=\"stage-name\">{{ item.entity[entityNameField] }}</span>\n @if (item.entity[entityCodeField]) {\n <span class=\"stage-code\">{{ t('bulk_delete_code_label') }}: {{ item.entity[entityCodeField] }}</span>\n }\n </div>\n </div>\n <div class=\"status-badge\">\n <p-tag \n [value]=\"getStatusLabel(item.status)\" \n [severity]=\"getStatusSeverity(item.status)\"\n >\n @if (item.status === 'processing') {\n <i class=\"pi pi-spin pi-spinner\"></i>\n }\n </p-tag>\n @if (item.status === 'error' && item.errorMessage) {\n <small class=\"error-message\">\n {{ item.errorMessage }}\n </small>\n }\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Completion Message -->\n @if (isCompleted) {\n <div class=\"completion-section\">\n <div class=\"completion-message\" [class.has-errors]=\"errorCount > 0\">\n <i [class]=\"errorCount > 0 ? 'pi pi-exclamation-circle' : 'pi pi-check-circle'\" class=\"completion-icon\"></i>\n @if (errorCount === 0) {\n <p>\n {{ t('bulk_delete_success_message') }}\n </p>\n }\n @if (errorCount > 0) {\n <p>\n {{ t('bulk_delete_completed_with_errors', { count: errorCount }) }}\n </p>\n }\n </div>\n </div>\n }\n </div>\n\n <ng-template pTemplate=\"footer\">\n <div class=\"dialog-footer\">\n @if (!isProcessing && !isCompleted) {\n <p-button\n [label]=\"t('cancel')\"\n icon=\"pi pi-times\"\n severity=\"secondary\"\n [outlined]=\"true\"\n (onClick)=\"onClose()\"\n ></p-button>\n <p-button\n [label]=\"t('bulk_delete_confirm')\"\n icon=\"pi pi-trash\"\n severity=\"danger\"\n (onClick)=\"startDeletion()\"\n ></p-button>\n }\n @if (isCompleted) {\n <p-button\n [label]=\"t('close')\"\n icon=\"pi pi-check\"\n (onClick)=\"onClose()\"\n ></p-button>\n }\n </div>\n </ng-template>\n</p-dialog>\n", styles: [".dialog-header{display:flex;align-items:center;gap:12px}.dialog-header .header-icon{font-size:24px;color:#ef4444}.dialog-header h2{margin:0;font-size:20px;font-weight:700;color:var(--neutral-color-800)}.dialog-content{padding:8px 0}.summary-section{padding:20px;background:linear-gradient(135deg,#fef3c7,#fde68a);border-radius:12px;border:1px solid #fbbf24;margin-bottom:24px}.summary-section .summary-text{display:flex;align-items:center;gap:12px;margin:0 0 12px;font-size:16px;color:var(--neutral-color-800)}.summary-section .summary-text .warning-icon{font-size:24px;color:#f59e0b}.summary-section .summary-text strong{color:#d97706}.summary-section .summary-description{margin:0;font-size:14px;color:var(--neutral-color-600);padding-left:36px}.progress-section{margin-bottom:24px}.progress-section .progress-info{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.progress-section .progress-info .progress-label{font-weight:600;font-size:14px;color:var(--neutral-color-800)}.progress-section .progress-info .progress-count{font-size:14px;color:var(--neutral-color-600);font-weight:500}.progress-section .progress-stats{display:flex;gap:20px;margin-top:12px}.progress-section .progress-stats .stat{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500}.progress-section .progress-stats .stat.success{color:#10b981}.progress-section .progress-stats .stat.success i{font-size:18px}.progress-section .progress-stats .stat.error{color:#ef4444}.progress-section .progress-stats .stat.error i{font-size:18px}.status-list{max-height:400px;overflow-y:auto;border:1px solid var(--p-surface-200);border-radius:12px;padding:12px;background-color:#fafafa}.status-list .status-item{display:flex;justify-content:space-between;align-items:center;padding:12px;background-color:#fff;border-radius:8px;margin-bottom:8px;transition:all .2s ease}.status-list .status-item:last-child{margin-bottom:0}.status-list .status-item:hover{box-shadow:0 2px 8px #00000014}.status-list .status-item .status-info{display:flex;align-items:center;gap:12px;flex:1}.status-list .status-item .status-info .status-icon{font-size:20px}.status-list .status-item .status-info .status-icon.pi-clock{color:var(--neutral-color-400)}.status-list .status-item .status-info .status-icon.pi-spinner{color:#3b82f6}.status-list .status-item .status-info .status-icon.pi-check{color:#10b981}.status-list .status-item .status-info .status-icon.pi-times{color:#ef4444}.status-list .status-item .status-info .status-details{display:flex;flex-direction:column;gap:4px}.status-list .status-item .status-info .status-details .stage-name{font-weight:600;font-size:14px;color:var(--neutral-color-800)}.status-list .status-item .status-info .status-details .stage-code{font-size:12px;color:var(--neutral-color-500)}.status-list .status-item .status-badge{display:flex;flex-direction:column;align-items:flex-end;gap:4px}.status-list .status-item .status-badge .error-message{font-size:11px;color:#ef4444;max-width:200px;text-align:right}.completion-section{margin-top:24px}.completion-section .completion-message{padding:20px;background:linear-gradient(135deg,#d1fae5,#a7f3d0);border-radius:12px;border:1px solid #10b981;display:flex;align-items:center;gap:16px}.completion-section .completion-message.has-errors{background:linear-gradient(135deg,#fee2e2,#fecaca);border-color:#ef4444}.completion-section .completion-message.has-errors .completion-icon{color:#ef4444}.completion-section .completion-message .completion-icon{font-size:32px;color:#10b981}.completion-section .completion-message p{margin:0;font-size:16px;font-weight:600;color:var(--neutral-color-800)}.dialog-footer{display:flex;justify-content:flex-end;gap:12px}::ng-deep .custom-progress{height:12px;border-radius:6px}::ng-deep .custom-progress .p-progressbar-value{background:linear-gradient(90deg,var(--p-primary-500) 0%,var(--p-primary-500) 100%);border-radius:6px}.status-list::-webkit-scrollbar{width:8px}.status-list::-webkit-scrollbar-track{background:var(--p-surface-100);border-radius:4px}.status-list::-webkit-scrollbar-thumb{background:var(--neutral-color-400);border-radius:4px}.status-list::-webkit-scrollbar-thumb:hover{background:var(--neutral-color-500)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i5.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i5$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i4.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: i5$2.Tag, selector: "p-tag", inputs: ["style", "styleClass", "severity", "value", "icon", "rounded"] }] });
|
|
1616
|
+
}
|
|
1617
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BulkDeleteDialogComponent, decorators: [{
|
|
1618
|
+
type: Component,
|
|
1619
|
+
args: [{ selector: 'sia-bulk-delete-dialog', standalone: true, imports: [
|
|
1620
|
+
CommonModule,
|
|
1621
|
+
DialogModule,
|
|
1622
|
+
ButtonModule,
|
|
1623
|
+
ProgressBarModule,
|
|
1624
|
+
TagModule
|
|
1625
|
+
], template: "<p-dialog\n [(visible)]=\"visible\"\n [modal]=\"true\"\n [closable]=\"canClose\"\n [closeOnEscape]=\"canClose\"\n [dismissableMask]=\"false\"\n [style]=\"{ width: '700px', maxWidth: '90vw' }\"\n (onHide)=\"onHide()\"\n>\n <ng-template pTemplate=\"header\">\n <div class=\"dialog-header\">\n <i class=\"pi pi-trash header-icon\"></i>\n <h2>{{ t('bulk_delete_title') }}</h2>\n </div>\n </ng-template>\n\n <div class=\"dialog-content\">\n <!-- Summary -->\n @if (!isProcessing && !isCompleted) {\n <div class=\"summary-section\">\n <p class=\"summary-text\">\n <i class=\"pi pi-exclamation-triangle warning-icon\"></i>\n <span [innerHTML]=\"t('bulk_delete_warning', { count: stages.length })\"></span>\n </p>\n <p class=\"summary-description\">\n {{ t('bulk_delete_description') }}\n </p>\n </div>\n }\n\n <!-- Progress Bar -->\n @if (isProcessing || isCompleted) {\n <div class=\"progress-section\">\n <div class=\"progress-info\">\n <span class=\"progress-label\">{{ t('bulk_delete_progress') }}</span>\n <span class=\"progress-count\">{{ currentIndex }} / {{ deleteStatuses.length }}</span>\n </div>\n <p-progressBar \n [value]=\"progress\" \n [showValue]=\"false\"\n styleClass=\"custom-progress\"\n ></p-progressBar>\n \n <div class=\"progress-stats\">\n <div class=\"stat success\">\n <i class=\"pi pi-check-circle\"></i>\n <span>{{ successCount }} {{ t('bulk_delete_success_count') }}</span>\n </div>\n @if (errorCount > 0) {\n <div class=\"stat error\">\n <i class=\"pi pi-times-circle\"></i>\n <span>{{ errorCount }} {{ t('bulk_delete_error_count') }}</span>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Status List -->\n @if (isProcessing || isCompleted) {\n <div class=\"status-list\">\n @for (item of deleteStatuses; track item.entity) {\n <div class=\"status-item\">\n <div class=\"status-info\">\n <i [class]=\"getStatusIcon(item.status)\" class=\"status-icon\"></i>\n <div class=\"status-details\">\n <span class=\"stage-name\">{{ item.entity[entityNameField] }}</span>\n @if (item.entity[entityCodeField]) {\n <span class=\"stage-code\">{{ t('bulk_delete_code_label') }}: {{ item.entity[entityCodeField] }}</span>\n }\n </div>\n </div>\n <div class=\"status-badge\">\n <p-tag \n [value]=\"getStatusLabel(item.status)\" \n [severity]=\"getStatusSeverity(item.status)\"\n >\n @if (item.status === 'processing') {\n <i class=\"pi pi-spin pi-spinner\"></i>\n }\n </p-tag>\n @if (item.status === 'error' && item.errorMessage) {\n <small class=\"error-message\">\n {{ item.errorMessage }}\n </small>\n }\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Completion Message -->\n @if (isCompleted) {\n <div class=\"completion-section\">\n <div class=\"completion-message\" [class.has-errors]=\"errorCount > 0\">\n <i [class]=\"errorCount > 0 ? 'pi pi-exclamation-circle' : 'pi pi-check-circle'\" class=\"completion-icon\"></i>\n @if (errorCount === 0) {\n <p>\n {{ t('bulk_delete_success_message') }}\n </p>\n }\n @if (errorCount > 0) {\n <p>\n {{ t('bulk_delete_completed_with_errors', { count: errorCount }) }}\n </p>\n }\n </div>\n </div>\n }\n </div>\n\n <ng-template pTemplate=\"footer\">\n <div class=\"dialog-footer\">\n @if (!isProcessing && !isCompleted) {\n <p-button\n [label]=\"t('cancel')\"\n icon=\"pi pi-times\"\n severity=\"secondary\"\n [outlined]=\"true\"\n (onClick)=\"onClose()\"\n ></p-button>\n <p-button\n [label]=\"t('bulk_delete_confirm')\"\n icon=\"pi pi-trash\"\n severity=\"danger\"\n (onClick)=\"startDeletion()\"\n ></p-button>\n }\n @if (isCompleted) {\n <p-button\n [label]=\"t('close')\"\n icon=\"pi pi-check\"\n (onClick)=\"onClose()\"\n ></p-button>\n }\n </div>\n </ng-template>\n</p-dialog>\n", styles: [".dialog-header{display:flex;align-items:center;gap:12px}.dialog-header .header-icon{font-size:24px;color:#ef4444}.dialog-header h2{margin:0;font-size:20px;font-weight:700;color:var(--neutral-color-800)}.dialog-content{padding:8px 0}.summary-section{padding:20px;background:linear-gradient(135deg,#fef3c7,#fde68a);border-radius:12px;border:1px solid #fbbf24;margin-bottom:24px}.summary-section .summary-text{display:flex;align-items:center;gap:12px;margin:0 0 12px;font-size:16px;color:var(--neutral-color-800)}.summary-section .summary-text .warning-icon{font-size:24px;color:#f59e0b}.summary-section .summary-text strong{color:#d97706}.summary-section .summary-description{margin:0;font-size:14px;color:var(--neutral-color-600);padding-left:36px}.progress-section{margin-bottom:24px}.progress-section .progress-info{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}.progress-section .progress-info .progress-label{font-weight:600;font-size:14px;color:var(--neutral-color-800)}.progress-section .progress-info .progress-count{font-size:14px;color:var(--neutral-color-600);font-weight:500}.progress-section .progress-stats{display:flex;gap:20px;margin-top:12px}.progress-section .progress-stats .stat{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:500}.progress-section .progress-stats .stat.success{color:#10b981}.progress-section .progress-stats .stat.success i{font-size:18px}.progress-section .progress-stats .stat.error{color:#ef4444}.progress-section .progress-stats .stat.error i{font-size:18px}.status-list{max-height:400px;overflow-y:auto;border:1px solid var(--p-surface-200);border-radius:12px;padding:12px;background-color:#fafafa}.status-list .status-item{display:flex;justify-content:space-between;align-items:center;padding:12px;background-color:#fff;border-radius:8px;margin-bottom:8px;transition:all .2s ease}.status-list .status-item:last-child{margin-bottom:0}.status-list .status-item:hover{box-shadow:0 2px 8px #00000014}.status-list .status-item .status-info{display:flex;align-items:center;gap:12px;flex:1}.status-list .status-item .status-info .status-icon{font-size:20px}.status-list .status-item .status-info .status-icon.pi-clock{color:var(--neutral-color-400)}.status-list .status-item .status-info .status-icon.pi-spinner{color:#3b82f6}.status-list .status-item .status-info .status-icon.pi-check{color:#10b981}.status-list .status-item .status-info .status-icon.pi-times{color:#ef4444}.status-list .status-item .status-info .status-details{display:flex;flex-direction:column;gap:4px}.status-list .status-item .status-info .status-details .stage-name{font-weight:600;font-size:14px;color:var(--neutral-color-800)}.status-list .status-item .status-info .status-details .stage-code{font-size:12px;color:var(--neutral-color-500)}.status-list .status-item .status-badge{display:flex;flex-direction:column;align-items:flex-end;gap:4px}.status-list .status-item .status-badge .error-message{font-size:11px;color:#ef4444;max-width:200px;text-align:right}.completion-section{margin-top:24px}.completion-section .completion-message{padding:20px;background:linear-gradient(135deg,#d1fae5,#a7f3d0);border-radius:12px;border:1px solid #10b981;display:flex;align-items:center;gap:16px}.completion-section .completion-message.has-errors{background:linear-gradient(135deg,#fee2e2,#fecaca);border-color:#ef4444}.completion-section .completion-message.has-errors .completion-icon{color:#ef4444}.completion-section .completion-message .completion-icon{font-size:32px;color:#10b981}.completion-section .completion-message p{margin:0;font-size:16px;font-weight:600;color:var(--neutral-color-800)}.dialog-footer{display:flex;justify-content:flex-end;gap:12px}::ng-deep .custom-progress{height:12px;border-radius:6px}::ng-deep .custom-progress .p-progressbar-value{background:linear-gradient(90deg,var(--p-primary-500) 0%,var(--p-primary-500) 100%);border-radius:6px}.status-list::-webkit-scrollbar{width:8px}.status-list::-webkit-scrollbar-track{background:var(--p-surface-100);border-radius:4px}.status-list::-webkit-scrollbar-thumb{background:var(--neutral-color-400);border-radius:4px}.status-list::-webkit-scrollbar-thumb:hover{background:var(--neutral-color-500)}\n"] }]
|
|
1626
|
+
}], ctorParameters: () => [{ type: undefined, decorators: [{
|
|
1627
|
+
type: Optional
|
|
1628
|
+
}, {
|
|
1629
|
+
type: Inject,
|
|
1630
|
+
args: ['TranslationService']
|
|
1631
|
+
}] }], propDecorators: { visible: [{
|
|
1632
|
+
type: Input
|
|
1633
|
+
}], stages: [{
|
|
1634
|
+
type: Input
|
|
1635
|
+
}], service: [{
|
|
1636
|
+
type: Input
|
|
1637
|
+
}], entityNameField: [{
|
|
1638
|
+
type: Input
|
|
1639
|
+
}], entityCodeField: [{
|
|
1640
|
+
type: Input
|
|
1641
|
+
}], translationPrefix: [{
|
|
1642
|
+
type: Input
|
|
1643
|
+
}], visibleChange: [{
|
|
1644
|
+
type: Output
|
|
1645
|
+
}], onComplete: [{
|
|
1646
|
+
type: Output
|
|
1647
|
+
}] } });
|
|
1648
|
+
|
|
1649
|
+
class BreadcrumbComponent {
|
|
1650
|
+
router;
|
|
1651
|
+
activatedRoute;
|
|
1652
|
+
translationService;
|
|
1653
|
+
translationPrefix = 'crmx.business';
|
|
1654
|
+
isProduction = false;
|
|
1655
|
+
breadcrumbItems = [];
|
|
1656
|
+
constructor(router, activatedRoute, translationService) {
|
|
1657
|
+
this.router = router;
|
|
1658
|
+
this.activatedRoute = activatedRoute;
|
|
1659
|
+
this.translationService = translationService;
|
|
1660
|
+
}
|
|
1661
|
+
ngOnInit() {
|
|
1662
|
+
this.buildBreadcrumbs();
|
|
1663
|
+
// Atualiza breadcrumbs quando a navegação muda
|
|
1664
|
+
this.router.events
|
|
1665
|
+
.pipe(filter(event => event instanceof NavigationEnd))
|
|
1666
|
+
.subscribe(() => this.buildBreadcrumbs());
|
|
1667
|
+
}
|
|
1668
|
+
get homeItem() {
|
|
1669
|
+
// Só mostra o link do home em desenvolvimento
|
|
1670
|
+
if (!this.isProduction) {
|
|
1671
|
+
return {
|
|
1672
|
+
icon: 'pi pi-home',
|
|
1673
|
+
routerLink: '/main',
|
|
1674
|
+
title: this.t('breadcrumb_home')
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
// Em produção, o home não tem link (apenas ícone)
|
|
1678
|
+
return {
|
|
1679
|
+
icon: 'pi pi-home',
|
|
1680
|
+
title: this.t('breadcrumb_home'),
|
|
1681
|
+
disabled: true
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
buildBreadcrumbs() {
|
|
1685
|
+
const breadcrumbs = [];
|
|
1686
|
+
let currentRoute = this.activatedRoute.root;
|
|
1687
|
+
let url = '';
|
|
1688
|
+
let lastBreadcrumbLabel = '';
|
|
1689
|
+
// Percorre toda a árvore de rotas
|
|
1690
|
+
while (currentRoute) {
|
|
1691
|
+
// Verifica se há segmentos de URL
|
|
1692
|
+
if (currentRoute.snapshot.url.length > 0) {
|
|
1693
|
+
const routeURL = currentRoute.snapshot.url.map(segment => segment.path).join('/');
|
|
1694
|
+
url += `/${routeURL}`;
|
|
1695
|
+
}
|
|
1696
|
+
// Verifica se a rota tem dados de breadcrumb
|
|
1697
|
+
const breadcrumbData = currentRoute.snapshot.data['breadcrumb'];
|
|
1698
|
+
// Adiciona o breadcrumb se existir e não for duplicado
|
|
1699
|
+
if (breadcrumbData) {
|
|
1700
|
+
const label = this.t(breadcrumbData.title);
|
|
1701
|
+
// Só adiciona se não for o mesmo label do anterior (evita duplicação)
|
|
1702
|
+
if (label !== lastBreadcrumbLabel) {
|
|
1703
|
+
breadcrumbs.push({
|
|
1704
|
+
label: label,
|
|
1705
|
+
icon: breadcrumbData.icon,
|
|
1706
|
+
routerLink: url || undefined
|
|
1707
|
+
});
|
|
1708
|
+
lastBreadcrumbLabel = label;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
// Move para a próxima rota filha
|
|
1712
|
+
currentRoute = currentRoute.firstChild;
|
|
1713
|
+
}
|
|
1714
|
+
// O último item não deve ter link (está desabilitado)
|
|
1715
|
+
if (breadcrumbs.length > 0) {
|
|
1716
|
+
const lastItem = breadcrumbs[breadcrumbs.length - 1];
|
|
1717
|
+
lastItem.routerLink = undefined;
|
|
1718
|
+
lastItem.disabled = true;
|
|
1719
|
+
}
|
|
1720
|
+
this.breadcrumbItems = breadcrumbs;
|
|
1721
|
+
}
|
|
1722
|
+
t(key) {
|
|
1723
|
+
if (this.translationService) {
|
|
1724
|
+
// Se a chave já tem o prefixo, usa diretamente
|
|
1725
|
+
if (key.includes('.')) {
|
|
1726
|
+
return this.translationService.translate(key);
|
|
1727
|
+
}
|
|
1728
|
+
// Caso contrário, adiciona o prefixo
|
|
1729
|
+
return this.translationService.translate(`${this.translationPrefix}.${key}`);
|
|
1730
|
+
}
|
|
1731
|
+
return key;
|
|
1732
|
+
}
|
|
1733
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BreadcrumbComponent, deps: [{ token: i1$1.Router }, { token: i1$1.ActivatedRoute }, { token: 'TranslationService', optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
1734
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: BreadcrumbComponent, isStandalone: true, selector: "sia-breadcrumb", inputs: { translationPrefix: "translationPrefix", isProduction: "isProduction" }, ngImport: i0, template: `
|
|
1735
|
+
<p-breadcrumb
|
|
1736
|
+
[model]="breadcrumbItems"
|
|
1737
|
+
[home]="homeItem"
|
|
1738
|
+
styleClass="custom-breadcrumb"
|
|
1739
|
+
></p-breadcrumb>
|
|
1740
|
+
`, isInline: true, styles: ["::ng-deep .p-breadcrumb{background:transparent;border:none;padding:0!important}::ng-deep .custom-breadcrumb{background:transparent;border:none;padding:0!important;.p-breadcrumb-list{background:transparent;padding:0!important;.p-menuitem{.p-menuitem-link{color:var(--text-secondary);text-decoration:none;font-size:14px;font-weight:500;padding:8px 12px;border-radius:6px;transition:all .2s ease;display:flex;align-items:center;&:hover{background:var(--p-primary-50);color:var(--p-primary-600)}.p-menuitem-icon{margin-right:8px;font-size:16px;display:inline-flex;align-items:center}}&:last-child .p-menuitem-link{color:var(--p-primary-600);font-weight:600;background:var(--p-primary-50)}}.p-breadcrumb-separator{color:var(--text-muted);margin:0 8px;font-size:14px}}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "ngmodule", type: BreadcrumbModule }, { kind: "component", type: i2$2.Breadcrumb, selector: "p-breadcrumb", inputs: ["model", "style", "styleClass", "home", "homeAriaLabel"], outputs: ["onItemClick"] }] });
|
|
1741
|
+
}
|
|
1742
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: BreadcrumbComponent, decorators: [{
|
|
1743
|
+
type: Component,
|
|
1744
|
+
args: [{ selector: 'sia-breadcrumb', standalone: true, imports: [CommonModule, RouterModule, BreadcrumbModule], template: `
|
|
1745
|
+
<p-breadcrumb
|
|
1746
|
+
[model]="breadcrumbItems"
|
|
1747
|
+
[home]="homeItem"
|
|
1748
|
+
styleClass="custom-breadcrumb"
|
|
1749
|
+
></p-breadcrumb>
|
|
1750
|
+
`, styles: ["::ng-deep .p-breadcrumb{background:transparent;border:none;padding:0!important}::ng-deep .custom-breadcrumb{background:transparent;border:none;padding:0!important;.p-breadcrumb-list{background:transparent;padding:0!important;.p-menuitem{.p-menuitem-link{color:var(--text-secondary);text-decoration:none;font-size:14px;font-weight:500;padding:8px 12px;border-radius:6px;transition:all .2s ease;display:flex;align-items:center;&:hover{background:var(--p-primary-50);color:var(--p-primary-600)}.p-menuitem-icon{margin-right:8px;font-size:16px;display:inline-flex;align-items:center}}&:last-child .p-menuitem-link{color:var(--p-primary-600);font-weight:600;background:var(--p-primary-50)}}.p-breadcrumb-separator{color:var(--text-muted);margin:0 8px;font-size:14px}}}\n"] }]
|
|
1751
|
+
}], ctorParameters: () => [{ type: i1$1.Router }, { type: i1$1.ActivatedRoute }, { type: undefined, decorators: [{
|
|
1752
|
+
type: Optional
|
|
1753
|
+
}, {
|
|
1754
|
+
type: Inject,
|
|
1755
|
+
args: ['TranslationService']
|
|
1756
|
+
}] }], propDecorators: { translationPrefix: [{
|
|
1757
|
+
type: Input
|
|
1758
|
+
}], isProduction: [{
|
|
1759
|
+
type: Input
|
|
1760
|
+
}] } });
|
|
1761
|
+
|
|
1762
|
+
class TranslatePipe {
|
|
1763
|
+
translationService;
|
|
1764
|
+
cdr;
|
|
1765
|
+
subscription;
|
|
1766
|
+
lastKey;
|
|
1767
|
+
lastParams;
|
|
1768
|
+
lastTranslation;
|
|
1769
|
+
isLoading = false;
|
|
1770
|
+
constructor(translationService, cdr) {
|
|
1771
|
+
this.translationService = translationService;
|
|
1772
|
+
this.cdr = cdr;
|
|
1773
|
+
}
|
|
1774
|
+
transform(key, params) {
|
|
1775
|
+
if (!key)
|
|
1776
|
+
return '';
|
|
1777
|
+
// Check if we need to update the translation
|
|
1778
|
+
if (this.shouldUpdate(key, params)) {
|
|
1779
|
+
this.updateTranslation(key, params);
|
|
1780
|
+
}
|
|
1781
|
+
return this.lastTranslation || key;
|
|
1782
|
+
}
|
|
1783
|
+
ngOnDestroy() {
|
|
1784
|
+
if (this.subscription) {
|
|
1785
|
+
this.subscription.unsubscribe();
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
shouldUpdate(key, params) {
|
|
1789
|
+
return this.lastKey !== key ||
|
|
1790
|
+
JSON.stringify(this.lastParams) !== JSON.stringify(params) ||
|
|
1791
|
+
!this.subscription;
|
|
1792
|
+
}
|
|
1793
|
+
updateTranslation(key, params) {
|
|
1794
|
+
this.lastKey = key;
|
|
1795
|
+
this.lastParams = params;
|
|
1796
|
+
if (this.subscription) {
|
|
1797
|
+
this.subscription.unsubscribe();
|
|
1798
|
+
}
|
|
1799
|
+
// Aguardar carregamento das traduções se necessário
|
|
1800
|
+
if (!this.translationService.isLanguageLoaded()) {
|
|
1801
|
+
if (!this.isLoading) {
|
|
1802
|
+
this.isLoading = true;
|
|
1803
|
+
this.translationService.waitForTranslations().then(() => {
|
|
1804
|
+
this.isLoading = false;
|
|
1805
|
+
this.lastTranslation = this.translationService.translate(key, params);
|
|
1806
|
+
this.cdr.markForCheck();
|
|
1807
|
+
}).catch(error => {
|
|
1808
|
+
this.isLoading = false;
|
|
1809
|
+
console.error('Error waiting for translations:', error);
|
|
1810
|
+
this.lastTranslation = key;
|
|
1811
|
+
this.cdr.markForCheck();
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
this.subscription = this.translationService.currentLanguage$.subscribe(() => {
|
|
1817
|
+
this.lastTranslation = this.translationService.translate(key, params);
|
|
1818
|
+
this.cdr.markForCheck();
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TranslatePipe, deps: [{ token: TranslationService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Pipe });
|
|
1822
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: TranslatePipe, isStandalone: true, name: "translate", pure: false });
|
|
1823
|
+
}
|
|
1824
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TranslatePipe, decorators: [{
|
|
1825
|
+
type: Pipe,
|
|
1826
|
+
args: [{
|
|
1827
|
+
name: 'translate',
|
|
1828
|
+
pure: false,
|
|
1829
|
+
standalone: true
|
|
1830
|
+
}]
|
|
1831
|
+
}], ctorParameters: () => [{ type: TranslationService }, { type: i0.ChangeDetectorRef }] });
|
|
1832
|
+
|
|
1833
|
+
class CepMaskDirective {
|
|
1834
|
+
el;
|
|
1835
|
+
constructor(el) {
|
|
1836
|
+
this.el = el;
|
|
1837
|
+
}
|
|
1838
|
+
onKeyPress(event) {
|
|
1839
|
+
// Permite apenas números
|
|
1840
|
+
const charCode = event.which ? event.which : event.keyCode;
|
|
1841
|
+
if (charCode > 31 && (charCode < 48 || charCode > 57)) {
|
|
1842
|
+
event.preventDefault();
|
|
1843
|
+
return false;
|
|
1844
|
+
}
|
|
1845
|
+
return true;
|
|
1846
|
+
}
|
|
1847
|
+
onInput(event) {
|
|
1848
|
+
const value = event.target.value;
|
|
1849
|
+
this.applyCepMask(value);
|
|
1850
|
+
}
|
|
1851
|
+
onBlur(event) {
|
|
1852
|
+
const value = event.target.value;
|
|
1853
|
+
this.applyCepMask(value);
|
|
1854
|
+
}
|
|
1855
|
+
onPaste(event) {
|
|
1856
|
+
event.preventDefault();
|
|
1857
|
+
const pastedText = event.clipboardData?.getData('text') || '';
|
|
1858
|
+
const numbers = pastedText.replace(/\D/g, '');
|
|
1859
|
+
this.applyCepMask(numbers);
|
|
1860
|
+
}
|
|
1861
|
+
applyCepMask(value) {
|
|
1862
|
+
if (!value) {
|
|
1863
|
+
this.el.nativeElement.value = '';
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
// Remove todos os caracteres não numéricos
|
|
1867
|
+
const numbers = value.replace(/\D/g, '');
|
|
1868
|
+
// Limita a 8 dígitos
|
|
1869
|
+
const limitedNumbers = numbers.substring(0, 8);
|
|
1870
|
+
// Aplica a máscara 00000-000
|
|
1871
|
+
let maskedValue = '';
|
|
1872
|
+
if (limitedNumbers.length === 0) {
|
|
1873
|
+
maskedValue = '';
|
|
1874
|
+
}
|
|
1875
|
+
else if (limitedNumbers.length <= 5) {
|
|
1876
|
+
maskedValue = limitedNumbers;
|
|
1877
|
+
}
|
|
1878
|
+
else {
|
|
1879
|
+
maskedValue = limitedNumbers.replace(/(\d{5})(\d{0,3})/, '$1-$2');
|
|
1880
|
+
}
|
|
1881
|
+
// Atualizar o valor do input
|
|
1882
|
+
this.el.nativeElement.value = maskedValue;
|
|
1883
|
+
// Disparar evento de input para atualizar o FormControl
|
|
1884
|
+
const inputEvent = new Event('input', { bubbles: true });
|
|
1885
|
+
this.el.nativeElement.dispatchEvent(inputEvent);
|
|
1886
|
+
}
|
|
1887
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CepMaskDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
1888
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: CepMaskDirective, isStandalone: true, selector: "[siaCepMask]", host: { listeners: { "keypress": "onKeyPress($event)", "input": "onInput($event)", "blur": "onBlur($event)", "paste": "onPaste($event)" } }, ngImport: i0 });
|
|
1889
|
+
}
|
|
1890
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CepMaskDirective, decorators: [{
|
|
1891
|
+
type: Directive,
|
|
1892
|
+
args: [{
|
|
1893
|
+
selector: '[siaCepMask]',
|
|
1894
|
+
standalone: true
|
|
1895
|
+
}]
|
|
1896
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { onKeyPress: [{
|
|
1897
|
+
type: HostListener,
|
|
1898
|
+
args: ['keypress', ['$event']]
|
|
1899
|
+
}], onInput: [{
|
|
1900
|
+
type: HostListener,
|
|
1901
|
+
args: ['input', ['$event']]
|
|
1902
|
+
}], onBlur: [{
|
|
1903
|
+
type: HostListener,
|
|
1904
|
+
args: ['blur', ['$event']]
|
|
1905
|
+
}], onPaste: [{
|
|
1906
|
+
type: HostListener,
|
|
1907
|
+
args: ['paste', ['$event']]
|
|
1908
|
+
}] } });
|
|
1909
|
+
|
|
1910
|
+
class PhoneMaskDirective {
|
|
1911
|
+
el;
|
|
1912
|
+
constructor(el) {
|
|
1913
|
+
this.el = el;
|
|
1914
|
+
}
|
|
1915
|
+
onInput(event) {
|
|
1916
|
+
const value = event.target.value;
|
|
1917
|
+
this.applyPhoneMask(value);
|
|
1918
|
+
}
|
|
1919
|
+
onBlur(event) {
|
|
1920
|
+
const value = event.target.value;
|
|
1921
|
+
this.applyPhoneMask(value);
|
|
1922
|
+
}
|
|
1923
|
+
applyPhoneMask(value) {
|
|
1924
|
+
if (!value)
|
|
1925
|
+
return;
|
|
1926
|
+
// Remove todos os caracteres não numéricos
|
|
1927
|
+
const numbers = value.replace(/\D/g, '');
|
|
1928
|
+
let maskedValue = '';
|
|
1929
|
+
if (numbers.length <= 10) {
|
|
1930
|
+
// Telefone fixo: (00) 0000-0000
|
|
1931
|
+
maskedValue = numbers.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3');
|
|
1932
|
+
}
|
|
1933
|
+
else if (numbers.length === 11) {
|
|
1934
|
+
// Celular: (00) 00000-0000
|
|
1935
|
+
maskedValue = numbers.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3');
|
|
1936
|
+
}
|
|
1937
|
+
else {
|
|
1938
|
+
// Limitar a 11 dígitos
|
|
1939
|
+
const limitedNumbers = numbers.substring(0, 11);
|
|
1940
|
+
maskedValue = limitedNumbers.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3');
|
|
1941
|
+
}
|
|
1942
|
+
// Atualizar o valor do input apenas se mudou
|
|
1943
|
+
if (maskedValue && maskedValue !== value) {
|
|
1944
|
+
this.el.nativeElement.value = maskedValue;
|
|
1945
|
+
// Disparar evento de input para atualizar o FormControl
|
|
1946
|
+
const inputEvent = new Event('input', { bubbles: true });
|
|
1947
|
+
this.el.nativeElement.dispatchEvent(inputEvent);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PhoneMaskDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
|
|
1951
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: PhoneMaskDirective, isStandalone: true, selector: "[siaPhoneMask]", host: { listeners: { "input": "onInput($event)", "blur": "onBlur($event)" } }, ngImport: i0 });
|
|
1952
|
+
}
|
|
1953
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PhoneMaskDirective, decorators: [{
|
|
1954
|
+
type: Directive,
|
|
1955
|
+
args: [{
|
|
1956
|
+
selector: '[siaPhoneMask]',
|
|
1957
|
+
standalone: true
|
|
1958
|
+
}]
|
|
1959
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { onInput: [{
|
|
1960
|
+
type: HostListener,
|
|
1961
|
+
args: ['input', ['$event']]
|
|
1962
|
+
}], onBlur: [{
|
|
1963
|
+
type: HostListener,
|
|
1964
|
+
args: ['blur', ['$event']]
|
|
1965
|
+
}] } });
|
|
1966
|
+
|
|
1967
|
+
class MaskService {
|
|
1968
|
+
/**
|
|
1969
|
+
* Aplica máscara de CPF (999.999.999-99) - 100% numérico
|
|
1970
|
+
*/
|
|
1971
|
+
applyCpfMask(value) {
|
|
1972
|
+
if (!value)
|
|
1973
|
+
return '';
|
|
1974
|
+
// Remove todos os caracteres não numéricos
|
|
1975
|
+
const numbers = value.replace(/\D/g, '');
|
|
1976
|
+
// Limita a 11 dígitos
|
|
1977
|
+
const limitedNumbers = numbers.substring(0, 11);
|
|
1978
|
+
// Aplica a máscara progressivamente
|
|
1979
|
+
if (limitedNumbers.length <= 3) {
|
|
1980
|
+
return limitedNumbers;
|
|
1981
|
+
}
|
|
1982
|
+
else if (limitedNumbers.length <= 6) {
|
|
1983
|
+
return limitedNumbers.replace(/(\d{3})(\d+)/, '$1.$2');
|
|
1984
|
+
}
|
|
1985
|
+
else if (limitedNumbers.length <= 9) {
|
|
1986
|
+
return limitedNumbers.replace(/(\d{3})(\d{3})(\d+)/, '$1.$2.$3');
|
|
1987
|
+
}
|
|
1988
|
+
else {
|
|
1989
|
+
return limitedNumbers.replace(/(\d{3})(\d{3})(\d{3})(\d+)/, '$1.$2.$3-$4');
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Aplica máscara de CNPJ alfanumérico (XX.XXX.XXX/XXXX-XX)
|
|
1994
|
+
* Aceita letras e números em todos os caracteres
|
|
1995
|
+
*/
|
|
1996
|
+
applyCnpjMask(value) {
|
|
1997
|
+
if (!value)
|
|
1998
|
+
return '';
|
|
1999
|
+
// Remove caracteres especiais, mantém letras e números
|
|
2000
|
+
const cleaned = value.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
|
|
2001
|
+
// Limita a 14 caracteres
|
|
2002
|
+
const limitedChars = cleaned.substring(0, 14);
|
|
2003
|
+
// Aplica a máscara progressivamente
|
|
2004
|
+
if (limitedChars.length <= 2) {
|
|
2005
|
+
return limitedChars;
|
|
2006
|
+
}
|
|
2007
|
+
else if (limitedChars.length <= 5) {
|
|
2008
|
+
return limitedChars.replace(/(\w{2})(\w+)/, '$1.$2');
|
|
2009
|
+
}
|
|
2010
|
+
else if (limitedChars.length <= 8) {
|
|
2011
|
+
return limitedChars.replace(/(\w{2})(\w{3})(\w+)/, '$1.$2.$3');
|
|
2012
|
+
}
|
|
2013
|
+
else if (limitedChars.length <= 12) {
|
|
2014
|
+
return limitedChars.replace(/(\w{2})(\w{3})(\w{3})(\w+)/, '$1.$2.$3/$4');
|
|
2015
|
+
}
|
|
2016
|
+
else {
|
|
2017
|
+
return limitedChars.replace(/(\w{2})(\w{3})(\w{3})(\w{4})(\w+)/, '$1.$2.$3/$4-$5');
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
/**
|
|
2021
|
+
* Aplica a máscara apropriada baseada no tipo de pessoa
|
|
2022
|
+
* CPF: 100% numérico (999.999.999-99)
|
|
2023
|
+
* CNPJ: alfanumérico (XX.XXX.XXX/XXXX-XX)
|
|
2024
|
+
*/
|
|
2025
|
+
applyDocumentMask(value, typePerson) {
|
|
2026
|
+
if (!value)
|
|
2027
|
+
return '';
|
|
2028
|
+
if (typePerson === 'NATURAL_PERSON') {
|
|
2029
|
+
return this.applyCpfMask(value);
|
|
2030
|
+
}
|
|
2031
|
+
else {
|
|
2032
|
+
return this.applyCnpjMask(value);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Remove a máscara de um documento (mantém apenas alfanuméricos)
|
|
2037
|
+
*/
|
|
2038
|
+
removeMask(value) {
|
|
2039
|
+
if (!value)
|
|
2040
|
+
return '';
|
|
2041
|
+
return value.replace(/[^a-zA-Z0-9]/g, '');
|
|
2042
|
+
}
|
|
2043
|
+
/**
|
|
2044
|
+
* Determina se um documento é CPF ou CNPJ baseado no comprimento
|
|
2045
|
+
*/
|
|
2046
|
+
getDocumentType(value) {
|
|
2047
|
+
if (!value)
|
|
2048
|
+
return 'unknown';
|
|
2049
|
+
const cleaned = this.removeMask(value);
|
|
2050
|
+
if (cleaned.length <= 11) {
|
|
2051
|
+
return 'cpf';
|
|
2052
|
+
}
|
|
2053
|
+
else if (cleaned.length <= 14) {
|
|
2054
|
+
return 'cnpj';
|
|
2055
|
+
}
|
|
2056
|
+
return 'unknown';
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Valida se um CPF tem formato válido (11 dígitos numéricos)
|
|
2060
|
+
*/
|
|
2061
|
+
isValidCpfFormat(value) {
|
|
2062
|
+
if (!value)
|
|
2063
|
+
return false;
|
|
2064
|
+
const cleaned = value.replace(/\D/g, '');
|
|
2065
|
+
return cleaned.length === 11 && /^\d{11}$/.test(cleaned);
|
|
2066
|
+
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Valida se um CNPJ tem formato válido (14 caracteres alfanuméricos)
|
|
2069
|
+
*/
|
|
2070
|
+
isValidCnpjFormat(value) {
|
|
2071
|
+
if (!value)
|
|
2072
|
+
return false;
|
|
2073
|
+
const cleaned = this.removeMask(value);
|
|
2074
|
+
return cleaned.length === 14;
|
|
2075
|
+
}
|
|
2076
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MaskService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2077
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MaskService, providedIn: 'root' });
|
|
2078
|
+
}
|
|
2079
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MaskService, decorators: [{
|
|
2080
|
+
type: Injectable,
|
|
2081
|
+
args: [{
|
|
2082
|
+
providedIn: 'root'
|
|
2083
|
+
}]
|
|
2084
|
+
}] });
|
|
2085
|
+
|
|
2086
|
+
class DocumentMaskDirective {
|
|
2087
|
+
el;
|
|
2088
|
+
control;
|
|
2089
|
+
maskService;
|
|
2090
|
+
documentType = 'auto';
|
|
2091
|
+
allowAlphanumeric = false;
|
|
2092
|
+
typePerson;
|
|
2093
|
+
constructor(el, control, maskService) {
|
|
2094
|
+
this.el = el;
|
|
2095
|
+
this.control = control;
|
|
2096
|
+
this.maskService = maskService;
|
|
2097
|
+
}
|
|
2098
|
+
ngOnInit() {
|
|
2099
|
+
// Aplicar máscara inicial se já houver valor
|
|
2100
|
+
if (this.control.value) {
|
|
2101
|
+
this.applyMask(this.control.value);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
onInput(event) {
|
|
2105
|
+
const value = event.target.value;
|
|
2106
|
+
this.applyMask(value);
|
|
2107
|
+
}
|
|
2108
|
+
onBlur(event) {
|
|
2109
|
+
const value = event.target.value;
|
|
2110
|
+
this.applyMask(value);
|
|
2111
|
+
}
|
|
2112
|
+
applyMask(value) {
|
|
2113
|
+
if (!value) {
|
|
2114
|
+
this.control.control?.setValue('', { emitEvent: false });
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
const masked = this.maskService.applyDocumentMask(value, this.typePerson || 'JURIDICAL_PERSON');
|
|
2118
|
+
this.control.control?.setValue(masked, { emitEvent: false });
|
|
2119
|
+
this.el.nativeElement.value = masked;
|
|
2120
|
+
}
|
|
2121
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DocumentMaskDirective, deps: [{ token: i0.ElementRef }, { token: i2$1.NgControl }, { token: MaskService }], target: i0.ɵɵFactoryTarget.Directive });
|
|
2122
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: DocumentMaskDirective, isStandalone: true, selector: "[siaDocumentMask]", inputs: { documentType: "documentType", allowAlphanumeric: "allowAlphanumeric", typePerson: "typePerson" }, host: { listeners: { "input": "onInput($event)", "blur": "onBlur($event)" } }, ngImport: i0 });
|
|
2123
|
+
}
|
|
2124
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DocumentMaskDirective, decorators: [{
|
|
2125
|
+
type: Directive,
|
|
2126
|
+
args: [{
|
|
2127
|
+
selector: '[siaDocumentMask]',
|
|
2128
|
+
standalone: true
|
|
2129
|
+
}]
|
|
2130
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: i2$1.NgControl }, { type: MaskService }], propDecorators: { documentType: [{
|
|
2131
|
+
type: Input
|
|
2132
|
+
}], allowAlphanumeric: [{
|
|
2133
|
+
type: Input
|
|
2134
|
+
}], typePerson: [{
|
|
2135
|
+
type: Input
|
|
2136
|
+
}], onInput: [{
|
|
2137
|
+
type: HostListener,
|
|
2138
|
+
args: ['input', ['$event']]
|
|
2139
|
+
}], onBlur: [{
|
|
2140
|
+
type: HostListener,
|
|
2141
|
+
args: ['blur', ['$event']]
|
|
2142
|
+
}] } });
|
|
2143
|
+
|
|
2144
|
+
/**
|
|
2145
|
+
* Componente wrapper para campos de formulário dinâmico
|
|
2146
|
+
* Centraliza a renderização de label, campo e mensagem de erro
|
|
2147
|
+
*/
|
|
2148
|
+
class DynamicFieldWrapperComponent {
|
|
2149
|
+
translationService;
|
|
2150
|
+
field;
|
|
2151
|
+
form;
|
|
2152
|
+
mode = 'form';
|
|
2153
|
+
constructor(translationService) {
|
|
2154
|
+
this.translationService = translationService;
|
|
2155
|
+
}
|
|
2156
|
+
isRequired() {
|
|
2157
|
+
return this.field.required === true && this.mode === 'form';
|
|
2158
|
+
}
|
|
2159
|
+
isFieldInvalid() {
|
|
2160
|
+
const control = this.form.get(this.field.field);
|
|
2161
|
+
return !!(control && control.invalid && (control.dirty || control.touched));
|
|
2162
|
+
}
|
|
2163
|
+
getFieldError() {
|
|
2164
|
+
const control = this.form.get(this.field.field);
|
|
2165
|
+
if (control?.hasError('required')) {
|
|
2166
|
+
return this.translationService.translate('crmx.business.required_field');
|
|
2167
|
+
}
|
|
2168
|
+
if (control?.hasError('minlength')) {
|
|
2169
|
+
const minLength = this.field.minLength || control.errors?.['minlength'].requiredLength;
|
|
2170
|
+
return this.translationService.translate('crmx.business.min_length_error', { length: minLength });
|
|
2171
|
+
}
|
|
2172
|
+
if (control?.hasError('maxlength')) {
|
|
2173
|
+
const maxLength = this.field.maxLength || control.errors?.['maxlength'].requiredLength;
|
|
2174
|
+
return this.translationService.translate('crmx.business.max_length_error', { length: maxLength });
|
|
2175
|
+
}
|
|
2176
|
+
if (control?.hasError('min')) {
|
|
2177
|
+
const min = this.field.min || control.errors?.['min'].min;
|
|
2178
|
+
return this.translationService.translate('crmx.business.min_value_error', { value: min });
|
|
2179
|
+
}
|
|
2180
|
+
if (control?.hasError('max')) {
|
|
2181
|
+
const max = this.field.max || control.errors?.['max'].max;
|
|
2182
|
+
return this.translationService.translate('crmx.business.max_value_error', { value: max });
|
|
2183
|
+
}
|
|
2184
|
+
return '';
|
|
2185
|
+
}
|
|
2186
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldWrapperComponent, deps: [{ token: TranslationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2187
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DynamicFieldWrapperComponent, isStandalone: true, selector: "sia-dynamic-field-wrapper", inputs: { field: "field", form: "form", mode: "mode" }, ngImport: i0, template: `
|
|
2188
|
+
<div class="field">
|
|
2189
|
+
<label [for]="field.field">
|
|
2190
|
+
{{ field.label | translate }}
|
|
2191
|
+
<span *ngIf="isRequired()" class="required-asterisk">*</span>
|
|
2192
|
+
</label>
|
|
2193
|
+
|
|
2194
|
+
<!-- Conteúdo do campo (projetado via ng-content) -->
|
|
2195
|
+
<ng-content></ng-content>
|
|
2196
|
+
|
|
2197
|
+
<small *ngIf="isFieldInvalid()" class="p-error">
|
|
2198
|
+
{{ getFieldError() }}
|
|
2199
|
+
</small>
|
|
2200
|
+
</div>
|
|
2201
|
+
`, isInline: true, styles: [".field{display:flex;flex-direction:column;gap:.5rem;label{font-weight:500;color:var(--text-color);.required-asterisk{color:var(--p-red-500);margin-left:.25rem}}small.p-error{margin-top:.25rem;color:var(--p-red-500)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
2202
|
+
}
|
|
2203
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldWrapperComponent, decorators: [{
|
|
2204
|
+
type: Component,
|
|
2205
|
+
args: [{ selector: 'sia-dynamic-field-wrapper', standalone: true, imports: [
|
|
2206
|
+
CommonModule,
|
|
2207
|
+
TranslatePipe
|
|
2208
|
+
], template: `
|
|
2209
|
+
<div class="field">
|
|
2210
|
+
<label [for]="field.field">
|
|
2211
|
+
{{ field.label | translate }}
|
|
2212
|
+
<span *ngIf="isRequired()" class="required-asterisk">*</span>
|
|
2213
|
+
</label>
|
|
2214
|
+
|
|
2215
|
+
<!-- Conteúdo do campo (projetado via ng-content) -->
|
|
2216
|
+
<ng-content></ng-content>
|
|
2217
|
+
|
|
2218
|
+
<small *ngIf="isFieldInvalid()" class="p-error">
|
|
2219
|
+
{{ getFieldError() }}
|
|
2220
|
+
</small>
|
|
2221
|
+
</div>
|
|
2222
|
+
`, styles: [".field{display:flex;flex-direction:column;gap:.5rem;label{font-weight:500;color:var(--text-color);.required-asterisk{color:var(--p-red-500);margin-left:.25rem}}small.p-error{margin-top:.25rem;color:var(--p-red-500)}}\n"] }]
|
|
2223
|
+
}], ctorParameters: () => [{ type: TranslationService }], propDecorators: { field: [{
|
|
2224
|
+
type: Input
|
|
2225
|
+
}], form: [{
|
|
2226
|
+
type: Input
|
|
2227
|
+
}], mode: [{
|
|
2228
|
+
type: Input
|
|
2229
|
+
}] } });
|
|
2230
|
+
|
|
2231
|
+
class DynamicFieldTextComponent {
|
|
2232
|
+
translationService;
|
|
2233
|
+
field;
|
|
2234
|
+
form;
|
|
2235
|
+
mode = 'form';
|
|
2236
|
+
constructor(translationService) {
|
|
2237
|
+
this.translationService = translationService;
|
|
2238
|
+
}
|
|
2239
|
+
isFieldInvalid() {
|
|
2240
|
+
const control = this.form.get(this.field.field);
|
|
2241
|
+
return !!(control && control.invalid && (control.dirty || control.touched));
|
|
2242
|
+
}
|
|
2243
|
+
isFieldDirty() {
|
|
2244
|
+
const control = this.form.get(this.field.field);
|
|
2245
|
+
return !!(control && control.dirty);
|
|
2246
|
+
}
|
|
2247
|
+
getPlaceholder() {
|
|
2248
|
+
if (this.mode === 'filter') {
|
|
2249
|
+
return this.translationService.translate('crmx.business.filter_search_placeholder', {
|
|
2250
|
+
label: this.field.label.toLowerCase()
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
return this.field.placeholder || '';
|
|
2254
|
+
}
|
|
2255
|
+
getDocumentType() {
|
|
2256
|
+
const typePersonValue = this.form?.get('typePerson')?.value;
|
|
2257
|
+
return typePersonValue || 'JURIDICAL_PERSON';
|
|
2258
|
+
}
|
|
2259
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldTextComponent, deps: [{ token: TranslationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2260
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DynamicFieldTextComponent, isStandalone: true, selector: "sia-dynamic-field-text", inputs: { field: "field", form: "form", mode: "mode" }, ngImport: i0, template: `
|
|
2261
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2262
|
+
<div [formGroup]="form">
|
|
2263
|
+
<input
|
|
2264
|
+
*ngIf="!field.mask"
|
|
2265
|
+
pInputText
|
|
2266
|
+
[id]="field.field"
|
|
2267
|
+
[formControlName]="field.field"
|
|
2268
|
+
[placeholder]="getPlaceholder()"
|
|
2269
|
+
[maxlength]="field.maxLength || 524288"
|
|
2270
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2271
|
+
[class.ng-dirty]="isFieldDirty()" />
|
|
2272
|
+
|
|
2273
|
+
<!-- Input com máscara CEP -->
|
|
2274
|
+
<input
|
|
2275
|
+
*ngIf="field.mask === 'cep'"
|
|
2276
|
+
pInputText
|
|
2277
|
+
appCepMask
|
|
2278
|
+
[id]="field.field"
|
|
2279
|
+
[formControlName]="field.field"
|
|
2280
|
+
[placeholder]="getPlaceholder()"
|
|
2281
|
+
[maxlength]="field.maxLength || 524288"
|
|
2282
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2283
|
+
[class.ng-dirty]="isFieldDirty()" />
|
|
2284
|
+
|
|
2285
|
+
<!-- Input com máscara Telefone -->
|
|
2286
|
+
<input
|
|
2287
|
+
*ngIf="field.mask === 'phone'"
|
|
2288
|
+
pInputText
|
|
2289
|
+
appPhoneMask
|
|
2290
|
+
[id]="field.field"
|
|
2291
|
+
[formControlName]="field.field"
|
|
2292
|
+
[placeholder]="getPlaceholder()"
|
|
2293
|
+
[maxlength]="field.maxLength || 524288"
|
|
2294
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2295
|
+
[class.ng-dirty]="isFieldDirty()" />
|
|
2296
|
+
|
|
2297
|
+
<!-- Input com máscara Documento -->
|
|
2298
|
+
<input
|
|
2299
|
+
*ngIf="field.mask === 'document'"
|
|
2300
|
+
pInputText
|
|
2301
|
+
appDocumentMask
|
|
2302
|
+
[id]="field.field"
|
|
2303
|
+
[formControlName]="field.field"
|
|
2304
|
+
[placeholder]="getPlaceholder()"
|
|
2305
|
+
[maxlength]="field.maxLength || 524288"
|
|
2306
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2307
|
+
[class.ng-dirty]="isFieldDirty()" />
|
|
2308
|
+
</div>
|
|
2309
|
+
</sia-dynamic-field-wrapper>
|
|
2310
|
+
`, isInline: true, styles: ["input{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "component", type: DynamicFieldWrapperComponent, selector: "sia-dynamic-field-wrapper", inputs: ["field", "form", "mode"] }] });
|
|
2311
|
+
}
|
|
2312
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldTextComponent, decorators: [{
|
|
2313
|
+
type: Component,
|
|
2314
|
+
args: [{ selector: 'sia-dynamic-field-text', standalone: true, imports: [
|
|
2315
|
+
CommonModule,
|
|
2316
|
+
ReactiveFormsModule,
|
|
2317
|
+
InputTextModule,
|
|
2318
|
+
CepMaskDirective,
|
|
2319
|
+
PhoneMaskDirective,
|
|
2320
|
+
DocumentMaskDirective,
|
|
2321
|
+
DynamicFieldWrapperComponent
|
|
2322
|
+
], template: `
|
|
2323
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2324
|
+
<div [formGroup]="form">
|
|
2325
|
+
<input
|
|
2326
|
+
*ngIf="!field.mask"
|
|
2327
|
+
pInputText
|
|
2328
|
+
[id]="field.field"
|
|
2329
|
+
[formControlName]="field.field"
|
|
2330
|
+
[placeholder]="getPlaceholder()"
|
|
2331
|
+
[maxlength]="field.maxLength || 524288"
|
|
2332
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2333
|
+
[class.ng-dirty]="isFieldDirty()" />
|
|
2334
|
+
|
|
2335
|
+
<!-- Input com máscara CEP -->
|
|
2336
|
+
<input
|
|
2337
|
+
*ngIf="field.mask === 'cep'"
|
|
2338
|
+
pInputText
|
|
2339
|
+
appCepMask
|
|
2340
|
+
[id]="field.field"
|
|
2341
|
+
[formControlName]="field.field"
|
|
2342
|
+
[placeholder]="getPlaceholder()"
|
|
2343
|
+
[maxlength]="field.maxLength || 524288"
|
|
2344
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2345
|
+
[class.ng-dirty]="isFieldDirty()" />
|
|
2346
|
+
|
|
2347
|
+
<!-- Input com máscara Telefone -->
|
|
2348
|
+
<input
|
|
2349
|
+
*ngIf="field.mask === 'phone'"
|
|
2350
|
+
pInputText
|
|
2351
|
+
appPhoneMask
|
|
2352
|
+
[id]="field.field"
|
|
2353
|
+
[formControlName]="field.field"
|
|
2354
|
+
[placeholder]="getPlaceholder()"
|
|
2355
|
+
[maxlength]="field.maxLength || 524288"
|
|
2356
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2357
|
+
[class.ng-dirty]="isFieldDirty()" />
|
|
2358
|
+
|
|
2359
|
+
<!-- Input com máscara Documento -->
|
|
2360
|
+
<input
|
|
2361
|
+
*ngIf="field.mask === 'document'"
|
|
2362
|
+
pInputText
|
|
2363
|
+
appDocumentMask
|
|
2364
|
+
[id]="field.field"
|
|
2365
|
+
[formControlName]="field.field"
|
|
2366
|
+
[placeholder]="getPlaceholder()"
|
|
2367
|
+
[maxlength]="field.maxLength || 524288"
|
|
2368
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2369
|
+
[class.ng-dirty]="isFieldDirty()" />
|
|
2370
|
+
</div>
|
|
2371
|
+
</sia-dynamic-field-wrapper>
|
|
2372
|
+
`, styles: ["input{width:100%}\n"] }]
|
|
2373
|
+
}], ctorParameters: () => [{ type: TranslationService }], propDecorators: { field: [{
|
|
2374
|
+
type: Input
|
|
2375
|
+
}], form: [{
|
|
2376
|
+
type: Input
|
|
2377
|
+
}], mode: [{
|
|
2378
|
+
type: Input
|
|
2379
|
+
}] } });
|
|
2380
|
+
|
|
2381
|
+
class DynamicFieldNumberComponent {
|
|
2382
|
+
translationService;
|
|
2383
|
+
field;
|
|
2384
|
+
form;
|
|
2385
|
+
mode = 'form';
|
|
2386
|
+
constructor(translationService) {
|
|
2387
|
+
this.translationService = translationService;
|
|
2388
|
+
}
|
|
2389
|
+
isFieldInvalid() {
|
|
2390
|
+
const control = this.form.get(this.field.field);
|
|
2391
|
+
return !!(control && control.invalid && (control.dirty || control.touched));
|
|
2392
|
+
}
|
|
2393
|
+
isFieldDirty() {
|
|
2394
|
+
const control = this.form.get(this.field.field);
|
|
2395
|
+
return !!(control && control.dirty);
|
|
2396
|
+
}
|
|
2397
|
+
getPlaceholder() {
|
|
2398
|
+
if (this.mode === 'filter') {
|
|
2399
|
+
return this.translationService.translate('crmx.business.filter_search_placeholder', {
|
|
2400
|
+
label: this.field.label.toLowerCase()
|
|
2401
|
+
});
|
|
2402
|
+
}
|
|
2403
|
+
return this.field.placeholder || '';
|
|
2404
|
+
}
|
|
2405
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldNumberComponent, deps: [{ token: TranslationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2406
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DynamicFieldNumberComponent, isStandalone: true, selector: "sia-dynamic-field-number", inputs: { field: "field", form: "form", mode: "mode" }, ngImport: i0, template: `
|
|
2407
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2408
|
+
<div [formGroup]="form">
|
|
2409
|
+
<p-inputNumber
|
|
2410
|
+
[inputId]="field.field"
|
|
2411
|
+
[formControlName]="field.field"
|
|
2412
|
+
[placeholder]="getPlaceholder()"
|
|
2413
|
+
[min]="field.min"
|
|
2414
|
+
[max]="field.max"
|
|
2415
|
+
[minFractionDigits]="field.minFractionDigits || 0"
|
|
2416
|
+
[maxFractionDigits]="field.maxFractionDigits || 2"
|
|
2417
|
+
[mode]="field.numberMode || 'decimal'"
|
|
2418
|
+
[currency]="field.currency || 'BRL'"
|
|
2419
|
+
[locale]="field.locale || 'pt-BR'"
|
|
2420
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2421
|
+
[class.ng-dirty]="isFieldDirty()"
|
|
2422
|
+
styleClass="w-full">
|
|
2423
|
+
</p-inputNumber>
|
|
2424
|
+
</div>
|
|
2425
|
+
</sia-dynamic-field-wrapper>
|
|
2426
|
+
`, isInline: true, styles: ["::ng-deep p-inputNumber{width:100%;.p-inputnumber,input{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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"] }, { kind: "component", type: DynamicFieldWrapperComponent, selector: "sia-dynamic-field-wrapper", inputs: ["field", "form", "mode"] }] });
|
|
2427
|
+
}
|
|
2428
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldNumberComponent, decorators: [{
|
|
2429
|
+
type: Component,
|
|
2430
|
+
args: [{ selector: 'sia-dynamic-field-number', standalone: true, imports: [
|
|
2431
|
+
CommonModule,
|
|
2432
|
+
ReactiveFormsModule,
|
|
2433
|
+
InputNumberModule,
|
|
2434
|
+
DynamicFieldWrapperComponent
|
|
2435
|
+
], template: `
|
|
2436
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2437
|
+
<div [formGroup]="form">
|
|
2438
|
+
<p-inputNumber
|
|
2439
|
+
[inputId]="field.field"
|
|
2440
|
+
[formControlName]="field.field"
|
|
2441
|
+
[placeholder]="getPlaceholder()"
|
|
2442
|
+
[min]="field.min"
|
|
2443
|
+
[max]="field.max"
|
|
2444
|
+
[minFractionDigits]="field.minFractionDigits || 0"
|
|
2445
|
+
[maxFractionDigits]="field.maxFractionDigits || 2"
|
|
2446
|
+
[mode]="field.numberMode || 'decimal'"
|
|
2447
|
+
[currency]="field.currency || 'BRL'"
|
|
2448
|
+
[locale]="field.locale || 'pt-BR'"
|
|
2449
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2450
|
+
[class.ng-dirty]="isFieldDirty()"
|
|
2451
|
+
styleClass="w-full">
|
|
2452
|
+
</p-inputNumber>
|
|
2453
|
+
</div>
|
|
2454
|
+
</sia-dynamic-field-wrapper>
|
|
2455
|
+
`, styles: ["::ng-deep p-inputNumber{width:100%;.p-inputnumber,input{width:100%}}\n"] }]
|
|
2456
|
+
}], ctorParameters: () => [{ type: TranslationService }], propDecorators: { field: [{
|
|
2457
|
+
type: Input
|
|
2458
|
+
}], form: [{
|
|
2459
|
+
type: Input
|
|
2460
|
+
}], mode: [{
|
|
2461
|
+
type: Input
|
|
2462
|
+
}] } });
|
|
2463
|
+
|
|
2464
|
+
class DynamicFieldDateComponent {
|
|
2465
|
+
translationService;
|
|
2466
|
+
field;
|
|
2467
|
+
form;
|
|
2468
|
+
mode = 'form';
|
|
2469
|
+
constructor(translationService) {
|
|
2470
|
+
this.translationService = translationService;
|
|
2471
|
+
}
|
|
2472
|
+
isFieldInvalid() {
|
|
2473
|
+
const control = this.form.get(this.field.field);
|
|
2474
|
+
return !!(control && control.invalid && (control.dirty || control.touched));
|
|
2475
|
+
}
|
|
2476
|
+
isFieldDirty() {
|
|
2477
|
+
const control = this.form.get(this.field.field);
|
|
2478
|
+
return !!(control && control.dirty);
|
|
2479
|
+
}
|
|
2480
|
+
getPlaceholder() {
|
|
2481
|
+
if (this.mode === 'filter') {
|
|
2482
|
+
return this.translationService.translate('crmx.business.filter_select_placeholder', {
|
|
2483
|
+
label: this.field.label.toLowerCase()
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
return this.field.placeholder || '';
|
|
2487
|
+
}
|
|
2488
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldDateComponent, deps: [{ token: TranslationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2489
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DynamicFieldDateComponent, isStandalone: true, selector: "sia-dynamic-field-date", inputs: { field: "field", form: "form", mode: "mode" }, ngImport: i0, template: `
|
|
2490
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2491
|
+
<div [formGroup]="form">
|
|
2492
|
+
<p-calendar
|
|
2493
|
+
[inputId]="field.field"
|
|
2494
|
+
[formControlName]="field.field"
|
|
2495
|
+
[placeholder]="getPlaceholder()"
|
|
2496
|
+
[dateFormat]="field.dateFormat || 'dd/mm/yy'"
|
|
2497
|
+
[showIcon]="true"
|
|
2498
|
+
[showButtonBar]="true"
|
|
2499
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2500
|
+
[class.ng-dirty]="isFieldDirty()"
|
|
2501
|
+
[appendTo]="'body'"
|
|
2502
|
+
styleClass="w-full">
|
|
2503
|
+
</p-calendar>
|
|
2504
|
+
</div>
|
|
2505
|
+
</sia-dynamic-field-wrapper>
|
|
2506
|
+
`, isInline: true, styles: ["::ng-deep p-calendar{width:100%;.p-calendar,input{width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: CalendarModule }, { kind: "component", type: i3$2.Calendar, selector: "p-calendar", 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", "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"] }, { kind: "component", type: DynamicFieldWrapperComponent, selector: "sia-dynamic-field-wrapper", inputs: ["field", "form", "mode"] }] });
|
|
2507
|
+
}
|
|
2508
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldDateComponent, decorators: [{
|
|
2509
|
+
type: Component,
|
|
2510
|
+
args: [{ selector: 'sia-dynamic-field-date', standalone: true, imports: [
|
|
2511
|
+
CommonModule,
|
|
2512
|
+
ReactiveFormsModule,
|
|
2513
|
+
CalendarModule,
|
|
2514
|
+
DynamicFieldWrapperComponent
|
|
2515
|
+
], template: `
|
|
2516
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2517
|
+
<div [formGroup]="form">
|
|
2518
|
+
<p-calendar
|
|
2519
|
+
[inputId]="field.field"
|
|
2520
|
+
[formControlName]="field.field"
|
|
2521
|
+
[placeholder]="getPlaceholder()"
|
|
2522
|
+
[dateFormat]="field.dateFormat || 'dd/mm/yy'"
|
|
2523
|
+
[showIcon]="true"
|
|
2524
|
+
[showButtonBar]="true"
|
|
2525
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2526
|
+
[class.ng-dirty]="isFieldDirty()"
|
|
2527
|
+
[appendTo]="'body'"
|
|
2528
|
+
styleClass="w-full">
|
|
2529
|
+
</p-calendar>
|
|
2530
|
+
</div>
|
|
2531
|
+
</sia-dynamic-field-wrapper>
|
|
2532
|
+
`, styles: ["::ng-deep p-calendar{width:100%;.p-calendar,input{width:100%}}\n"] }]
|
|
2533
|
+
}], ctorParameters: () => [{ type: TranslationService }], propDecorators: { field: [{
|
|
2534
|
+
type: Input
|
|
2535
|
+
}], form: [{
|
|
2536
|
+
type: Input
|
|
2537
|
+
}], mode: [{
|
|
2538
|
+
type: Input
|
|
2539
|
+
}] } });
|
|
2540
|
+
|
|
2541
|
+
class DynamicFieldDropdownComponent {
|
|
2542
|
+
translationService;
|
|
2543
|
+
field;
|
|
2544
|
+
form;
|
|
2545
|
+
mode = 'form';
|
|
2546
|
+
constructor(translationService) {
|
|
2547
|
+
this.translationService = translationService;
|
|
2548
|
+
}
|
|
2549
|
+
isFieldInvalid() {
|
|
2550
|
+
const control = this.form.get(this.field.field);
|
|
2551
|
+
return !!(control && control.invalid && (control.dirty || control.touched));
|
|
2552
|
+
}
|
|
2553
|
+
isFieldDirty() {
|
|
2554
|
+
const control = this.form.get(this.field.field);
|
|
2555
|
+
return !!(control && control.dirty);
|
|
2556
|
+
}
|
|
2557
|
+
getPlaceholder() {
|
|
2558
|
+
if (this.mode === 'filter') {
|
|
2559
|
+
return this.translationService.translate('crmx.business.filter_select_placeholder', {
|
|
2560
|
+
label: this.field.label.toLowerCase()
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
return this.field.placeholder || this.translationService.translate('crmx.business.select_option');
|
|
2564
|
+
}
|
|
2565
|
+
getOptions() {
|
|
2566
|
+
if (this.field.type === 'enum' && this.field.enumValues) {
|
|
2567
|
+
return this.field.enumValues.map(value => ({
|
|
2568
|
+
label: this.field.enumLabels?.[value] || value,
|
|
2569
|
+
value: value
|
|
2570
|
+
}));
|
|
2571
|
+
}
|
|
2572
|
+
return this.field.options || [];
|
|
2573
|
+
}
|
|
2574
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldDropdownComponent, deps: [{ token: TranslationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2575
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DynamicFieldDropdownComponent, isStandalone: true, selector: "sia-dynamic-field-dropdown", inputs: { field: "field", form: "form", mode: "mode" }, ngImport: i0, template: `
|
|
2576
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2577
|
+
<div [formGroup]="form">
|
|
2578
|
+
<p-dropdown
|
|
2579
|
+
[inputId]="field.field"
|
|
2580
|
+
[formControlName]="field.field"
|
|
2581
|
+
[options]="getOptions()"
|
|
2582
|
+
[optionLabel]="field.optionLabel || 'label'"
|
|
2583
|
+
[optionValue]="field.optionValue || 'value'"
|
|
2584
|
+
[placeholder]="getPlaceholder()"
|
|
2585
|
+
[showClear]="mode === 'filter' || field.showClear"
|
|
2586
|
+
[filter]="field.filter || false"
|
|
2587
|
+
[filterBy]="field.filterBy"
|
|
2588
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2589
|
+
[class.ng-dirty]="isFieldDirty()"
|
|
2590
|
+
[appendTo]="'body'"
|
|
2591
|
+
panelStyleClass="dynamic-form-dropdown-panel"
|
|
2592
|
+
styleClass="w-full">
|
|
2593
|
+
</p-dropdown>
|
|
2594
|
+
</div>
|
|
2595
|
+
</sia-dynamic-field-wrapper>
|
|
2596
|
+
`, isInline: true, styles: ["::ng-deep p-dropdown{width:100%;.p-dropdown{width:100%}}.dynamic-form-dropdown-panel{min-width:100%!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i3$3.Dropdown, selector: "p-dropdown", 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", "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"] }, { kind: "component", type: DynamicFieldWrapperComponent, selector: "sia-dynamic-field-wrapper", inputs: ["field", "form", "mode"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
2597
|
+
}
|
|
2598
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldDropdownComponent, decorators: [{
|
|
2599
|
+
type: Component,
|
|
2600
|
+
args: [{ selector: 'sia-dynamic-field-dropdown', standalone: true, imports: [
|
|
2601
|
+
CommonModule,
|
|
2602
|
+
ReactiveFormsModule,
|
|
2603
|
+
DropdownModule,
|
|
2604
|
+
DynamicFieldWrapperComponent
|
|
2605
|
+
], template: `
|
|
2606
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2607
|
+
<div [formGroup]="form">
|
|
2608
|
+
<p-dropdown
|
|
2609
|
+
[inputId]="field.field"
|
|
2610
|
+
[formControlName]="field.field"
|
|
2611
|
+
[options]="getOptions()"
|
|
2612
|
+
[optionLabel]="field.optionLabel || 'label'"
|
|
2613
|
+
[optionValue]="field.optionValue || 'value'"
|
|
2614
|
+
[placeholder]="getPlaceholder()"
|
|
2615
|
+
[showClear]="mode === 'filter' || field.showClear"
|
|
2616
|
+
[filter]="field.filter || false"
|
|
2617
|
+
[filterBy]="field.filterBy"
|
|
2618
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
2619
|
+
[class.ng-dirty]="isFieldDirty()"
|
|
2620
|
+
[appendTo]="'body'"
|
|
2621
|
+
panelStyleClass="dynamic-form-dropdown-panel"
|
|
2622
|
+
styleClass="w-full">
|
|
2623
|
+
</p-dropdown>
|
|
2624
|
+
</div>
|
|
2625
|
+
</sia-dynamic-field-wrapper>
|
|
2626
|
+
`, encapsulation: ViewEncapsulation.None, styles: ["::ng-deep p-dropdown{width:100%;.p-dropdown{width:100%}}.dynamic-form-dropdown-panel{min-width:100%!important}\n"] }]
|
|
2627
|
+
}], ctorParameters: () => [{ type: TranslationService }], propDecorators: { field: [{
|
|
2628
|
+
type: Input
|
|
2629
|
+
}], form: [{
|
|
2630
|
+
type: Input
|
|
2631
|
+
}], mode: [{
|
|
2632
|
+
type: Input
|
|
2633
|
+
}] } });
|
|
2634
|
+
|
|
2635
|
+
class DynamicFieldLookupComponent {
|
|
2636
|
+
translationService;
|
|
2637
|
+
field;
|
|
2638
|
+
form;
|
|
2639
|
+
formGroup;
|
|
2640
|
+
mode = 'form';
|
|
2641
|
+
displayValue = '';
|
|
2642
|
+
showLookupDialog = false;
|
|
2643
|
+
loading = false;
|
|
2644
|
+
searchTerm = '';
|
|
2645
|
+
data = [];
|
|
2646
|
+
filteredData = [];
|
|
2647
|
+
totalRecords = 0;
|
|
2648
|
+
currentPage = 0;
|
|
2649
|
+
pageSize = 5;
|
|
2650
|
+
selectedItem = null;
|
|
2651
|
+
columns = [];
|
|
2652
|
+
displayField = 'name';
|
|
2653
|
+
valueField = 'id';
|
|
2654
|
+
searchFields = ['name'];
|
|
2655
|
+
constructor(translationService) {
|
|
2656
|
+
this.translationService = translationService;
|
|
2657
|
+
}
|
|
2658
|
+
ngOnInit() {
|
|
2659
|
+
this.setupLookupConfig();
|
|
2660
|
+
this.watchValueChanges();
|
|
2661
|
+
}
|
|
2662
|
+
watchValueChanges() {
|
|
2663
|
+
const control = this.form.get(this.field.field);
|
|
2664
|
+
if (control) {
|
|
2665
|
+
// Carregar valor inicial
|
|
2666
|
+
if (control.value) {
|
|
2667
|
+
this.handleValueDisplay(control.value);
|
|
2668
|
+
}
|
|
2669
|
+
// Observar mudanças
|
|
2670
|
+
control.valueChanges.subscribe(value => {
|
|
2671
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
2672
|
+
this.handleValueDisplay(value);
|
|
2673
|
+
}
|
|
2674
|
+
else {
|
|
2675
|
+
this.displayValue = '';
|
|
2676
|
+
}
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
get value() {
|
|
2681
|
+
return this.form.get(this.field.field)?.value;
|
|
2682
|
+
}
|
|
2683
|
+
get currentValue() {
|
|
2684
|
+
return this.value;
|
|
2685
|
+
}
|
|
2686
|
+
setValue(value) {
|
|
2687
|
+
this.form.get(this.field.field)?.setValue(value);
|
|
2688
|
+
}
|
|
2689
|
+
setupLookupConfig() {
|
|
2690
|
+
if (this.field) {
|
|
2691
|
+
this.displayField = this.field.lookupDisplayField || 'name';
|
|
2692
|
+
this.valueField = this.field.lookupValueField || 'id';
|
|
2693
|
+
this.searchFields = this.field.lookupSearchFields || ['name'];
|
|
2694
|
+
this.columns = this.field.lookupColumns || [
|
|
2695
|
+
{ field: this.displayField, header: 'Nome', width: '100%' }
|
|
2696
|
+
];
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
handleValueDisplay(value) {
|
|
2700
|
+
if (value === null || value === undefined || value === '') {
|
|
2701
|
+
this.displayValue = '';
|
|
2702
|
+
return;
|
|
2703
|
+
}
|
|
2704
|
+
if (typeof value === 'object' && value !== null) {
|
|
2705
|
+
this.displayValue = this.formatDisplayValue(value);
|
|
2706
|
+
// Em modo filter, manter o objeto completo
|
|
2707
|
+
// Em modo form, extrair apenas o ID EXCETO se já temos o objeto completo
|
|
2708
|
+
// (evita requisições desnecessárias quando o objeto já está carregado)
|
|
2709
|
+
if (this.mode === 'form') {
|
|
2710
|
+
// Verificar se o objeto tem o campo de display preenchido
|
|
2711
|
+
// Se tiver, significa que já temos os dados completos e não precisamos fazer nada
|
|
2712
|
+
const hasDisplayData = this.field?.lookupDisplayFields
|
|
2713
|
+
? this.field.lookupDisplayFields.some(f => this.getFieldValue(value, f))
|
|
2714
|
+
: value[this.displayField];
|
|
2715
|
+
if (!hasDisplayData) {
|
|
2716
|
+
// Só extrair o ID se não temos os dados de display
|
|
2717
|
+
const newValue = value[this.valueField] || value.id || value;
|
|
2718
|
+
if (this.value !== newValue) {
|
|
2719
|
+
this.setValue(newValue);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
// Se é um ID (string ou number), não fazer requisição
|
|
2726
|
+
// Deixar vazio e só carregar quando abrir o lookup
|
|
2727
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
2728
|
+
this.displayValue = '';
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
formatDisplayValue(item) {
|
|
2733
|
+
if (!item)
|
|
2734
|
+
return '';
|
|
2735
|
+
if (this.field?.lookupDisplayFields && this.field.lookupDisplayFields.length > 0) {
|
|
2736
|
+
const values = this.field.lookupDisplayFields
|
|
2737
|
+
.map(fieldName => this.getFieldValue(item, fieldName))
|
|
2738
|
+
.filter(value => value !== null && value !== undefined && value !== '');
|
|
2739
|
+
return values.join(' - ');
|
|
2740
|
+
}
|
|
2741
|
+
return this.getFieldValue(item, this.displayField) || '';
|
|
2742
|
+
}
|
|
2743
|
+
isFieldInvalid() {
|
|
2744
|
+
const control = this.form.get(this.field.field);
|
|
2745
|
+
return !!(control && control.invalid && (control.dirty || control.touched));
|
|
2746
|
+
}
|
|
2747
|
+
isFieldDisabled() {
|
|
2748
|
+
const control = this.form.get(this.field.field);
|
|
2749
|
+
return control?.disabled || false;
|
|
2750
|
+
}
|
|
2751
|
+
getPlaceholder() {
|
|
2752
|
+
if (this.mode === 'filter') {
|
|
2753
|
+
return this.translationService.translate('crmx.business.filter_search_placeholder', {
|
|
2754
|
+
label: this.field.label.toLowerCase()
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
return this.field.placeholder || 'Clique para selecionar';
|
|
2758
|
+
}
|
|
2759
|
+
openLookup() {
|
|
2760
|
+
if (this.isFieldDisabled()) {
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
this.updateSelectedItem();
|
|
2764
|
+
this.showLookupDialog = true;
|
|
2765
|
+
this.currentPage = 0;
|
|
2766
|
+
this.searchTerm = '';
|
|
2767
|
+
this.loadData();
|
|
2768
|
+
}
|
|
2769
|
+
onDialogHide() {
|
|
2770
|
+
this.searchTerm = '';
|
|
2771
|
+
this.currentPage = 0;
|
|
2772
|
+
}
|
|
2773
|
+
onPageChange(event) {
|
|
2774
|
+
if (!this.showLookupDialog) {
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
this.currentPage = event.first / event.rows;
|
|
2778
|
+
this.loadData();
|
|
2779
|
+
}
|
|
2780
|
+
loadData() {
|
|
2781
|
+
if (!this.field?.lookupService) {
|
|
2782
|
+
return;
|
|
2783
|
+
}
|
|
2784
|
+
this.loading = true;
|
|
2785
|
+
const listParams = {
|
|
2786
|
+
page: this.currentPage,
|
|
2787
|
+
size: this.pageSize
|
|
2788
|
+
};
|
|
2789
|
+
if (this.searchTerm.trim()) {
|
|
2790
|
+
const term = this.searchTerm.trim();
|
|
2791
|
+
const filterConditions = [];
|
|
2792
|
+
const isNumericTerm = !isNaN(Number(term)) && term !== '';
|
|
2793
|
+
this.searchFields.forEach(field => {
|
|
2794
|
+
const column = this.columns.find(col => col.field === field);
|
|
2795
|
+
const fieldType = column?.type || 'text';
|
|
2796
|
+
if (fieldType === 'number') {
|
|
2797
|
+
if (isNumericTerm) {
|
|
2798
|
+
filterConditions.push(`${field} eq ${term}`);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
else {
|
|
2802
|
+
const upperTerm = term.toUpperCase();
|
|
2803
|
+
filterConditions.push(`UPPER(${field}) like '%${upperTerm}%'`);
|
|
2804
|
+
}
|
|
2805
|
+
});
|
|
2806
|
+
if (filterConditions.length > 0) {
|
|
2807
|
+
listParams.filterQuery = filterConditions.join(' or ');
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
if (this.field.lookupFilters) {
|
|
2811
|
+
Object.keys(this.field.lookupFilters).forEach(key => {
|
|
2812
|
+
listParams[key] = this.field.lookupFilters[key];
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
if (this.field.lookupDynamicFilters && this.formGroup) {
|
|
2816
|
+
const dynamicFilters = this.field.lookupDynamicFilters(this.formGroup.value);
|
|
2817
|
+
const dynamicFilterConditions = [];
|
|
2818
|
+
Object.keys(dynamicFilters).forEach(key => {
|
|
2819
|
+
const value = dynamicFilters[key];
|
|
2820
|
+
if (value !== null && value !== undefined) {
|
|
2821
|
+
dynamicFilterConditions.push(`${key} eq '${value}'`);
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2824
|
+
if (dynamicFilterConditions.length > 0) {
|
|
2825
|
+
const dynamicFilterQuery = dynamicFilterConditions.join(' and ');
|
|
2826
|
+
if (listParams.filterQuery) {
|
|
2827
|
+
listParams.filterQuery = `(${listParams.filterQuery}) and (${dynamicFilterQuery})`;
|
|
2828
|
+
}
|
|
2829
|
+
else {
|
|
2830
|
+
listParams.filterQuery = dynamicFilterQuery;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
this.field.lookupService.list(listParams).subscribe({
|
|
2835
|
+
next: (response) => {
|
|
2836
|
+
this.data = response.contents || [];
|
|
2837
|
+
this.filteredData = [...this.data];
|
|
2838
|
+
this.totalRecords = response.totalElements || 0;
|
|
2839
|
+
this.updateSelectedItem();
|
|
2840
|
+
// Após carregar os dados da grid, se temos um valor selecionado mas sem displayValue,
|
|
2841
|
+
// tentar encontrar o item nos dados carregados
|
|
2842
|
+
if (this.value && !this.displayValue) {
|
|
2843
|
+
const item = this.filteredData.find(i => i[this.valueField] === this.value);
|
|
2844
|
+
if (item) {
|
|
2845
|
+
this.displayValue = this.formatDisplayValue(item);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
this.loading = false;
|
|
2849
|
+
},
|
|
2850
|
+
error: (_error) => {
|
|
2851
|
+
this.data = [];
|
|
2852
|
+
this.filteredData = [];
|
|
2853
|
+
this.totalRecords = 0;
|
|
2854
|
+
this.loading = false;
|
|
2855
|
+
}
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
updateSelectedItem() {
|
|
2859
|
+
if (!this.value) {
|
|
2860
|
+
this.selectedItem = null;
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
const item = this.filteredData.find(i => i[this.valueField] === this.value);
|
|
2864
|
+
if (item) {
|
|
2865
|
+
this.selectedItem = item;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
onSearch() {
|
|
2869
|
+
if (!this.showLookupDialog) {
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
this.currentPage = 0;
|
|
2873
|
+
this.loadData();
|
|
2874
|
+
}
|
|
2875
|
+
clearSearch() {
|
|
2876
|
+
this.searchTerm = '';
|
|
2877
|
+
this.currentPage = 0;
|
|
2878
|
+
this.loadData();
|
|
2879
|
+
}
|
|
2880
|
+
selectItem(item) {
|
|
2881
|
+
if (this.mode === 'filter') {
|
|
2882
|
+
this.setValue(item);
|
|
2883
|
+
this.displayValue = this.formatDisplayValue(item);
|
|
2884
|
+
}
|
|
2885
|
+
else {
|
|
2886
|
+
const newValue = item[this.valueField];
|
|
2887
|
+
this.setValue(newValue);
|
|
2888
|
+
this.displayValue = this.formatDisplayValue(item);
|
|
2889
|
+
}
|
|
2890
|
+
this.showLookupDialog = false;
|
|
2891
|
+
}
|
|
2892
|
+
onRowSelect(event) {
|
|
2893
|
+
this.selectItem(event.data);
|
|
2894
|
+
}
|
|
2895
|
+
clearValue() {
|
|
2896
|
+
this.setValue(null);
|
|
2897
|
+
this.displayValue = '';
|
|
2898
|
+
this.selectedItem = null;
|
|
2899
|
+
}
|
|
2900
|
+
getFieldValue(item, field) {
|
|
2901
|
+
return field.split('.').reduce((obj, key) => obj?.[key], item) || '';
|
|
2902
|
+
}
|
|
2903
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldLookupComponent, deps: [{ token: TranslationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2904
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DynamicFieldLookupComponent, isStandalone: true, selector: "sia-dynamic-field-lookup", inputs: { field: "field", form: "form", formGroup: "formGroup", mode: "mode" }, ngImport: i0, template: `
|
|
2905
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
2906
|
+
<div [formGroup]="form" class="lookup-field" [ngClass]="{'disabled': isFieldDisabled()}">
|
|
2907
|
+
<!-- Hidden input para o formControl -->
|
|
2908
|
+
<input type="hidden" [formControlName]="field.field" />
|
|
2909
|
+
|
|
2910
|
+
<div class="lookup-input-group">
|
|
2911
|
+
<input
|
|
2912
|
+
pInputText
|
|
2913
|
+
[value]="displayValue"
|
|
2914
|
+
[placeholder]="getPlaceholder()"
|
|
2915
|
+
[class.p-invalid]="isFieldInvalid()"
|
|
2916
|
+
[disabled]="isFieldDisabled()"
|
|
2917
|
+
readonly
|
|
2918
|
+
class="lookup-display"
|
|
2919
|
+
(click)="openLookup()"
|
|
2920
|
+
/>
|
|
2921
|
+
<div class="lookup-buttons">
|
|
2922
|
+
<p-button
|
|
2923
|
+
*ngIf="currentValue"
|
|
2924
|
+
icon="pi pi-trash"
|
|
2925
|
+
severity="danger"
|
|
2926
|
+
[text]="true"
|
|
2927
|
+
[rounded]="true"
|
|
2928
|
+
(onClick)="clearValue()"
|
|
2929
|
+
[disabled]="isFieldDisabled()"
|
|
2930
|
+
pTooltip="Limpar"
|
|
2931
|
+
tooltipPosition="top"
|
|
2932
|
+
size="small"
|
|
2933
|
+
></p-button>
|
|
2934
|
+
<p-button
|
|
2935
|
+
icon="pi pi-search"
|
|
2936
|
+
severity="secondary"
|
|
2937
|
+
[text]="true"
|
|
2938
|
+
[rounded]="true"
|
|
2939
|
+
(onClick)="openLookup()"
|
|
2940
|
+
[disabled]="isFieldDisabled()"
|
|
2941
|
+
pTooltip="Buscar"
|
|
2942
|
+
tooltipPosition="top"
|
|
2943
|
+
size="small"
|
|
2944
|
+
></p-button>
|
|
2945
|
+
</div>
|
|
2946
|
+
</div>
|
|
2947
|
+
</div>
|
|
2948
|
+
|
|
2949
|
+
<!-- Lookup Dialog -->
|
|
2950
|
+
<p-dialog
|
|
2951
|
+
[(visible)]="showLookupDialog"
|
|
2952
|
+
[modal]="true"
|
|
2953
|
+
[style]="{ width: '800px', maxHeight: '80vh' }"
|
|
2954
|
+
[contentStyle]="{ maxHeight: 'calc(80vh - 120px)', overflow: 'auto' }"
|
|
2955
|
+
[draggable]="false"
|
|
2956
|
+
[resizable]="true"
|
|
2957
|
+
[header]="'Selecionar ' + (field.label | translate)"
|
|
2958
|
+
[appendTo]="'body'"
|
|
2959
|
+
(onHide)="onDialogHide()"
|
|
2960
|
+
>
|
|
2961
|
+
<div class="lookup-dialog-content">
|
|
2962
|
+
<!-- Search Input -->
|
|
2963
|
+
<div class="search-wrapper">
|
|
2964
|
+
<input
|
|
2965
|
+
pInputText
|
|
2966
|
+
[(ngModel)]="searchTerm"
|
|
2967
|
+
[placeholder]="'Buscar ' + (field.label | translate).toLowerCase() + '...'"
|
|
2968
|
+
(keydown.enter)="onSearch()"
|
|
2969
|
+
class="search-input-full"
|
|
2970
|
+
/>
|
|
2971
|
+
<div class="search-buttons-inside">
|
|
2972
|
+
<p-button
|
|
2973
|
+
*ngIf="searchTerm"
|
|
2974
|
+
icon="pi pi-trash"
|
|
2975
|
+
severity="danger"
|
|
2976
|
+
[text]="true"
|
|
2977
|
+
[rounded]="true"
|
|
2978
|
+
(onClick)="clearSearch()"
|
|
2979
|
+
class="search-button-clear"
|
|
2980
|
+
pTooltip="Limpar busca"
|
|
2981
|
+
tooltipPosition="top"
|
|
2982
|
+
></p-button>
|
|
2983
|
+
<p-button
|
|
2984
|
+
icon="pi pi-search"
|
|
2985
|
+
severity="secondary"
|
|
2986
|
+
[text]="true"
|
|
2987
|
+
[rounded]="true"
|
|
2988
|
+
(onClick)="onSearch()"
|
|
2989
|
+
class="search-button-search"
|
|
2990
|
+
pTooltip="Buscar"
|
|
2991
|
+
tooltipPosition="top"
|
|
2992
|
+
></p-button>
|
|
2993
|
+
</div>
|
|
2994
|
+
</div>
|
|
2995
|
+
|
|
2996
|
+
<!-- Data Table -->
|
|
2997
|
+
<p-table
|
|
2998
|
+
[value]="filteredData"
|
|
2999
|
+
[loading]="loading"
|
|
3000
|
+
[paginator]="true"
|
|
3001
|
+
[rows]="pageSize"
|
|
3002
|
+
[totalRecords]="totalRecords"
|
|
3003
|
+
[lazy]="true"
|
|
3004
|
+
(onLazyLoad)="onPageChange($event)"
|
|
3005
|
+
[showCurrentPageReport]="true"
|
|
3006
|
+
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} registros"
|
|
3007
|
+
dataKey="id"
|
|
3008
|
+
[rowHover]="true"
|
|
3009
|
+
responsiveLayout="scroll"
|
|
3010
|
+
styleClass="p-datatable-striped lookup-table"
|
|
3011
|
+
[(selection)]="selectedItem"
|
|
3012
|
+
(onRowSelect)="onRowSelect($event)"
|
|
3013
|
+
selectionMode="single"
|
|
3014
|
+
>
|
|
3015
|
+
<ng-template pTemplate="header">
|
|
3016
|
+
<tr>
|
|
3017
|
+
<th *ngFor="let col of columns" [style.width]="col.width">
|
|
3018
|
+
{{ col.header }}
|
|
3019
|
+
</th>
|
|
3020
|
+
</tr>
|
|
3021
|
+
</ng-template>
|
|
3022
|
+
|
|
3023
|
+
<ng-template pTemplate="body" let-item>
|
|
3024
|
+
<tr [pSelectableRow]="item">
|
|
3025
|
+
<td *ngFor="let col of columns">
|
|
3026
|
+
{{ getFieldValue(item, col.field) }}
|
|
3027
|
+
</td>
|
|
3028
|
+
</tr>
|
|
3029
|
+
</ng-template>
|
|
3030
|
+
|
|
3031
|
+
<ng-template pTemplate="emptymessage">
|
|
3032
|
+
<tr>
|
|
3033
|
+
<td [attr.colspan]="columns.length" class="empty-message">
|
|
3034
|
+
<div class="empty-state">
|
|
3035
|
+
<i class="pi pi-search"></i>
|
|
3036
|
+
<p>Nenhum registro encontrado</p>
|
|
3037
|
+
<small>Tente ajustar os termos de busca</small>
|
|
3038
|
+
</div>
|
|
3039
|
+
</td>
|
|
3040
|
+
</tr>
|
|
3041
|
+
</ng-template>
|
|
3042
|
+
</p-table>
|
|
3043
|
+
</div>
|
|
3044
|
+
</p-dialog>
|
|
3045
|
+
</sia-dynamic-field-wrapper>
|
|
3046
|
+
`, isInline: true, styles: [".lookup-field{width:100%;position:relative}.lookup-input-group{position:relative;width:100%;.lookup-display{width:100%;cursor:pointer;height:2.857rem;padding:.75rem 4rem .75rem .75rem;font-size:1rem;line-height:1.5;box-sizing:border-box;display:flex;align-items:center;&:focus{outline:none}&:hover{background:transparent}}.lookup-buttons{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:flex;gap:2px;z-index:10}::ng-deep .p-button{width:2rem!important;height:2rem!important;min-width:2rem!important;min-height:2rem!important;padding:0!important;margin:0!important;display:flex!important;align-items:center!important;justify-content:center!important;box-sizing:border-box!important;.p-button-icon{margin:0!important;font-size:.875rem!important}.p-button-label{display:none!important}}}.lookup-dialog-content{height:100%;display:flex;flex-direction:column;padding:0;.search-wrapper{position:relative;margin-bottom:16px;width:100%;.search-input-full{width:100%;padding-right:5.5rem}.search-buttons-inside{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:flex;gap:2px;::ng-deep .p-button{width:2.5rem!important;height:2.5rem!important}}}.lookup-table{flex:1;.empty-state{text-align:center;padding:32px;color:var(--p-text-color-secondary);i{font-size:48px;margin-bottom:16px;display:block}p{margin:8px 0;font-size:16px}small{font-size:14px}}}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i5$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i5.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4$1.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i8.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "style", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "scrollDirection", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "responsive", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "autoLayout", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "virtualRowHeight", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "directive", type: i8.SelectableRow, selector: "[pSelectableRow]", inputs: ["pSelectableRow", "pSelectableRowIndex", "pSelectableRowDisabled"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i9.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: DynamicFieldWrapperComponent, selector: "sia-dynamic-field-wrapper", inputs: ["field", "form", "mode"] }] });
|
|
3047
|
+
}
|
|
3048
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldLookupComponent, decorators: [{
|
|
3049
|
+
type: Component,
|
|
3050
|
+
args: [{ selector: 'sia-dynamic-field-lookup', standalone: true, imports: [
|
|
3051
|
+
CommonModule,
|
|
3052
|
+
ReactiveFormsModule,
|
|
3053
|
+
FormsModule,
|
|
3054
|
+
ButtonModule,
|
|
3055
|
+
InputTextModule,
|
|
3056
|
+
DialogModule,
|
|
3057
|
+
TableModule,
|
|
3058
|
+
TooltipModule,
|
|
3059
|
+
TranslatePipe,
|
|
3060
|
+
DynamicFieldWrapperComponent
|
|
3061
|
+
], template: `
|
|
3062
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
3063
|
+
<div [formGroup]="form" class="lookup-field" [ngClass]="{'disabled': isFieldDisabled()}">
|
|
3064
|
+
<!-- Hidden input para o formControl -->
|
|
3065
|
+
<input type="hidden" [formControlName]="field.field" />
|
|
3066
|
+
|
|
3067
|
+
<div class="lookup-input-group">
|
|
3068
|
+
<input
|
|
3069
|
+
pInputText
|
|
3070
|
+
[value]="displayValue"
|
|
3071
|
+
[placeholder]="getPlaceholder()"
|
|
3072
|
+
[class.p-invalid]="isFieldInvalid()"
|
|
3073
|
+
[disabled]="isFieldDisabled()"
|
|
3074
|
+
readonly
|
|
3075
|
+
class="lookup-display"
|
|
3076
|
+
(click)="openLookup()"
|
|
3077
|
+
/>
|
|
3078
|
+
<div class="lookup-buttons">
|
|
3079
|
+
<p-button
|
|
3080
|
+
*ngIf="currentValue"
|
|
3081
|
+
icon="pi pi-trash"
|
|
3082
|
+
severity="danger"
|
|
3083
|
+
[text]="true"
|
|
3084
|
+
[rounded]="true"
|
|
3085
|
+
(onClick)="clearValue()"
|
|
3086
|
+
[disabled]="isFieldDisabled()"
|
|
3087
|
+
pTooltip="Limpar"
|
|
3088
|
+
tooltipPosition="top"
|
|
3089
|
+
size="small"
|
|
3090
|
+
></p-button>
|
|
3091
|
+
<p-button
|
|
3092
|
+
icon="pi pi-search"
|
|
3093
|
+
severity="secondary"
|
|
3094
|
+
[text]="true"
|
|
3095
|
+
[rounded]="true"
|
|
3096
|
+
(onClick)="openLookup()"
|
|
3097
|
+
[disabled]="isFieldDisabled()"
|
|
3098
|
+
pTooltip="Buscar"
|
|
3099
|
+
tooltipPosition="top"
|
|
3100
|
+
size="small"
|
|
3101
|
+
></p-button>
|
|
3102
|
+
</div>
|
|
3103
|
+
</div>
|
|
3104
|
+
</div>
|
|
3105
|
+
|
|
3106
|
+
<!-- Lookup Dialog -->
|
|
3107
|
+
<p-dialog
|
|
3108
|
+
[(visible)]="showLookupDialog"
|
|
3109
|
+
[modal]="true"
|
|
3110
|
+
[style]="{ width: '800px', maxHeight: '80vh' }"
|
|
3111
|
+
[contentStyle]="{ maxHeight: 'calc(80vh - 120px)', overflow: 'auto' }"
|
|
3112
|
+
[draggable]="false"
|
|
3113
|
+
[resizable]="true"
|
|
3114
|
+
[header]="'Selecionar ' + (field.label | translate)"
|
|
3115
|
+
[appendTo]="'body'"
|
|
3116
|
+
(onHide)="onDialogHide()"
|
|
3117
|
+
>
|
|
3118
|
+
<div class="lookup-dialog-content">
|
|
3119
|
+
<!-- Search Input -->
|
|
3120
|
+
<div class="search-wrapper">
|
|
3121
|
+
<input
|
|
3122
|
+
pInputText
|
|
3123
|
+
[(ngModel)]="searchTerm"
|
|
3124
|
+
[placeholder]="'Buscar ' + (field.label | translate).toLowerCase() + '...'"
|
|
3125
|
+
(keydown.enter)="onSearch()"
|
|
3126
|
+
class="search-input-full"
|
|
3127
|
+
/>
|
|
3128
|
+
<div class="search-buttons-inside">
|
|
3129
|
+
<p-button
|
|
3130
|
+
*ngIf="searchTerm"
|
|
3131
|
+
icon="pi pi-trash"
|
|
3132
|
+
severity="danger"
|
|
3133
|
+
[text]="true"
|
|
3134
|
+
[rounded]="true"
|
|
3135
|
+
(onClick)="clearSearch()"
|
|
3136
|
+
class="search-button-clear"
|
|
3137
|
+
pTooltip="Limpar busca"
|
|
3138
|
+
tooltipPosition="top"
|
|
3139
|
+
></p-button>
|
|
3140
|
+
<p-button
|
|
3141
|
+
icon="pi pi-search"
|
|
3142
|
+
severity="secondary"
|
|
3143
|
+
[text]="true"
|
|
3144
|
+
[rounded]="true"
|
|
3145
|
+
(onClick)="onSearch()"
|
|
3146
|
+
class="search-button-search"
|
|
3147
|
+
pTooltip="Buscar"
|
|
3148
|
+
tooltipPosition="top"
|
|
3149
|
+
></p-button>
|
|
3150
|
+
</div>
|
|
3151
|
+
</div>
|
|
3152
|
+
|
|
3153
|
+
<!-- Data Table -->
|
|
3154
|
+
<p-table
|
|
3155
|
+
[value]="filteredData"
|
|
3156
|
+
[loading]="loading"
|
|
3157
|
+
[paginator]="true"
|
|
3158
|
+
[rows]="pageSize"
|
|
3159
|
+
[totalRecords]="totalRecords"
|
|
3160
|
+
[lazy]="true"
|
|
3161
|
+
(onLazyLoad)="onPageChange($event)"
|
|
3162
|
+
[showCurrentPageReport]="true"
|
|
3163
|
+
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} registros"
|
|
3164
|
+
dataKey="id"
|
|
3165
|
+
[rowHover]="true"
|
|
3166
|
+
responsiveLayout="scroll"
|
|
3167
|
+
styleClass="p-datatable-striped lookup-table"
|
|
3168
|
+
[(selection)]="selectedItem"
|
|
3169
|
+
(onRowSelect)="onRowSelect($event)"
|
|
3170
|
+
selectionMode="single"
|
|
3171
|
+
>
|
|
3172
|
+
<ng-template pTemplate="header">
|
|
3173
|
+
<tr>
|
|
3174
|
+
<th *ngFor="let col of columns" [style.width]="col.width">
|
|
3175
|
+
{{ col.header }}
|
|
3176
|
+
</th>
|
|
3177
|
+
</tr>
|
|
3178
|
+
</ng-template>
|
|
3179
|
+
|
|
3180
|
+
<ng-template pTemplate="body" let-item>
|
|
3181
|
+
<tr [pSelectableRow]="item">
|
|
3182
|
+
<td *ngFor="let col of columns">
|
|
3183
|
+
{{ getFieldValue(item, col.field) }}
|
|
3184
|
+
</td>
|
|
3185
|
+
</tr>
|
|
3186
|
+
</ng-template>
|
|
3187
|
+
|
|
3188
|
+
<ng-template pTemplate="emptymessage">
|
|
3189
|
+
<tr>
|
|
3190
|
+
<td [attr.colspan]="columns.length" class="empty-message">
|
|
3191
|
+
<div class="empty-state">
|
|
3192
|
+
<i class="pi pi-search"></i>
|
|
3193
|
+
<p>Nenhum registro encontrado</p>
|
|
3194
|
+
<small>Tente ajustar os termos de busca</small>
|
|
3195
|
+
</div>
|
|
3196
|
+
</td>
|
|
3197
|
+
</tr>
|
|
3198
|
+
</ng-template>
|
|
3199
|
+
</p-table>
|
|
3200
|
+
</div>
|
|
3201
|
+
</p-dialog>
|
|
3202
|
+
</sia-dynamic-field-wrapper>
|
|
3203
|
+
`, styles: [".lookup-field{width:100%;position:relative}.lookup-input-group{position:relative;width:100%;.lookup-display{width:100%;cursor:pointer;height:2.857rem;padding:.75rem 4rem .75rem .75rem;font-size:1rem;line-height:1.5;box-sizing:border-box;display:flex;align-items:center;&:focus{outline:none}&:hover{background:transparent}}.lookup-buttons{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:flex;gap:2px;z-index:10}::ng-deep .p-button{width:2rem!important;height:2rem!important;min-width:2rem!important;min-height:2rem!important;padding:0!important;margin:0!important;display:flex!important;align-items:center!important;justify-content:center!important;box-sizing:border-box!important;.p-button-icon{margin:0!important;font-size:.875rem!important}.p-button-label{display:none!important}}}.lookup-dialog-content{height:100%;display:flex;flex-direction:column;padding:0;.search-wrapper{position:relative;margin-bottom:16px;width:100%;.search-input-full{width:100%;padding-right:5.5rem}.search-buttons-inside{position:absolute;right:4px;top:50%;transform:translateY(-50%);display:flex;gap:2px;::ng-deep .p-button{width:2.5rem!important;height:2.5rem!important}}}.lookup-table{flex:1;.empty-state{text-align:center;padding:32px;color:var(--p-text-color-secondary);i{font-size:48px;margin-bottom:16px;display:block}p{margin:8px 0;font-size:16px}small{font-size:14px}}}}\n"] }]
|
|
3204
|
+
}], ctorParameters: () => [{ type: TranslationService }], propDecorators: { field: [{
|
|
3205
|
+
type: Input
|
|
3206
|
+
}], form: [{
|
|
3207
|
+
type: Input
|
|
3208
|
+
}], formGroup: [{
|
|
3209
|
+
type: Input
|
|
3210
|
+
}], mode: [{
|
|
3211
|
+
type: Input
|
|
3212
|
+
}] } });
|
|
3213
|
+
|
|
3214
|
+
class DynamicFieldTextareaComponent {
|
|
3215
|
+
translationService;
|
|
3216
|
+
field;
|
|
3217
|
+
form;
|
|
3218
|
+
mode = 'form';
|
|
3219
|
+
constructor(translationService) {
|
|
3220
|
+
this.translationService = translationService;
|
|
3221
|
+
}
|
|
3222
|
+
isFieldInvalid() {
|
|
3223
|
+
const control = this.form.get(this.field.field);
|
|
3224
|
+
return !!(control && control.invalid && (control.dirty || control.touched));
|
|
3225
|
+
}
|
|
3226
|
+
isFieldDirty() {
|
|
3227
|
+
const control = this.form.get(this.field.field);
|
|
3228
|
+
return !!(control && control.dirty);
|
|
3229
|
+
}
|
|
3230
|
+
getPlaceholder() {
|
|
3231
|
+
if (this.mode === 'filter') {
|
|
3232
|
+
return this.translationService.translate('crmx.business.filter_search_placeholder', {
|
|
3233
|
+
label: this.field.label.toLowerCase()
|
|
3234
|
+
});
|
|
3235
|
+
}
|
|
3236
|
+
return this.field.placeholder || '';
|
|
3237
|
+
}
|
|
3238
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldTextareaComponent, deps: [{ token: TranslationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3239
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DynamicFieldTextareaComponent, isStandalone: true, selector: "sia-dynamic-field-textarea", inputs: { field: "field", form: "form", mode: "mode" }, ngImport: i0, template: `
|
|
3240
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
3241
|
+
<div [formGroup]="form">
|
|
3242
|
+
<textarea
|
|
3243
|
+
pInputTextarea
|
|
3244
|
+
[id]="field.field"
|
|
3245
|
+
[formControlName]="field.field"
|
|
3246
|
+
[placeholder]="getPlaceholder()"
|
|
3247
|
+
[rows]="field.rows || 3"
|
|
3248
|
+
[maxlength]="field.maxLength || 524288"
|
|
3249
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
3250
|
+
[class.ng-dirty]="isFieldDirty()">
|
|
3251
|
+
</textarea>
|
|
3252
|
+
</div>
|
|
3253
|
+
</sia-dynamic-field-wrapper>
|
|
3254
|
+
`, isInline: true, styles: ["textarea{width:100%;resize:vertical}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.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: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: InputTextarea, selector: "[pInputTextarea]", inputs: ["autoResize", "variant", "fluid"], outputs: ["onResize"] }, { kind: "component", type: DynamicFieldWrapperComponent, selector: "sia-dynamic-field-wrapper", inputs: ["field", "form", "mode"] }] });
|
|
3255
|
+
}
|
|
3256
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFieldTextareaComponent, decorators: [{
|
|
3257
|
+
type: Component,
|
|
3258
|
+
args: [{ selector: 'sia-dynamic-field-textarea', standalone: true, imports: [
|
|
3259
|
+
CommonModule,
|
|
3260
|
+
ReactiveFormsModule,
|
|
3261
|
+
InputTextarea,
|
|
3262
|
+
DynamicFieldWrapperComponent
|
|
3263
|
+
], template: `
|
|
3264
|
+
<sia-dynamic-field-wrapper [field]="field" [form]="form" [mode]="mode">
|
|
3265
|
+
<div [formGroup]="form">
|
|
3266
|
+
<textarea
|
|
3267
|
+
pInputTextarea
|
|
3268
|
+
[id]="field.field"
|
|
3269
|
+
[formControlName]="field.field"
|
|
3270
|
+
[placeholder]="getPlaceholder()"
|
|
3271
|
+
[rows]="field.rows || 3"
|
|
3272
|
+
[maxlength]="field.maxLength || 524288"
|
|
3273
|
+
[class.ng-invalid]="isFieldInvalid()"
|
|
3274
|
+
[class.ng-dirty]="isFieldDirty()">
|
|
3275
|
+
</textarea>
|
|
3276
|
+
</div>
|
|
3277
|
+
</sia-dynamic-field-wrapper>
|
|
3278
|
+
`, styles: ["textarea{width:100%;resize:vertical}\n"] }]
|
|
3279
|
+
}], ctorParameters: () => [{ type: TranslationService }], propDecorators: { field: [{
|
|
3280
|
+
type: Input
|
|
3281
|
+
}], form: [{
|
|
3282
|
+
type: Input
|
|
3283
|
+
}], mode: [{
|
|
3284
|
+
type: Input
|
|
3285
|
+
}] } });
|
|
3286
|
+
|
|
3287
|
+
class DynamicFormComponent {
|
|
3288
|
+
fb;
|
|
3289
|
+
translationService;
|
|
3290
|
+
formFields = [];
|
|
3291
|
+
formSections = [];
|
|
3292
|
+
fixedFilters = []; // Filtros fixos (sempre visíveis no topo)
|
|
3293
|
+
filterSections = []; // Seções de filtros (colapsáveis)
|
|
3294
|
+
entityData = null;
|
|
3295
|
+
mode = 'form';
|
|
3296
|
+
// Display Mode Configuration
|
|
3297
|
+
displayMode = 'inline';
|
|
3298
|
+
visible = false;
|
|
3299
|
+
dialogConfig = {};
|
|
3300
|
+
drawerConfig = {};
|
|
3301
|
+
// Button Configuration
|
|
3302
|
+
showSubmitButton = false;
|
|
3303
|
+
showCancelButton = false;
|
|
3304
|
+
submitButtonLabel;
|
|
3305
|
+
cancelButtonLabel;
|
|
3306
|
+
submitButtonIcon = 'pi pi-check';
|
|
3307
|
+
cancelButtonIcon = 'pi pi-times';
|
|
3308
|
+
formReady = new EventEmitter();
|
|
3309
|
+
formSubmit = new EventEmitter();
|
|
3310
|
+
visibleChange = new EventEmitter();
|
|
3311
|
+
onCancel = new EventEmitter();
|
|
3312
|
+
fieldSave = new EventEmitter();
|
|
3313
|
+
form;
|
|
3314
|
+
loading = false;
|
|
3315
|
+
// Inline edit mode state
|
|
3316
|
+
editingField = null;
|
|
3317
|
+
savingField = false;
|
|
3318
|
+
get hasFields() {
|
|
3319
|
+
return this.formFields.length > 0;
|
|
3320
|
+
}
|
|
3321
|
+
get hasSections() {
|
|
3322
|
+
return this.formSections.length > 0;
|
|
3323
|
+
}
|
|
3324
|
+
get hasFixedFilters() {
|
|
3325
|
+
return this.fixedFilters.length > 0;
|
|
3326
|
+
}
|
|
3327
|
+
get hasFilterSections() {
|
|
3328
|
+
return this.filterSections.length > 0;
|
|
3329
|
+
}
|
|
3330
|
+
get allFields() {
|
|
3331
|
+
// Retorna todos os campos (diretos + das seções + filtros fixos + seções de filtros)
|
|
3332
|
+
const sectionFields = this.formSections.flatMap(section => section.fields);
|
|
3333
|
+
const filterSectionFields = this.filterSections.flatMap(section => section.fields);
|
|
3334
|
+
return [...this.formFields, ...sectionFields, ...this.fixedFilters, ...filterSectionFields];
|
|
3335
|
+
}
|
|
3336
|
+
get isInlineMode() {
|
|
3337
|
+
return this.displayMode === 'inline';
|
|
3338
|
+
}
|
|
3339
|
+
get isInlineEditMode() {
|
|
3340
|
+
return this.mode === 'inline-edit';
|
|
3341
|
+
}
|
|
3342
|
+
get fieldMode() {
|
|
3343
|
+
return this.isInlineEditMode ? 'form' : this.mode;
|
|
3344
|
+
}
|
|
3345
|
+
get isDialogMode() {
|
|
3346
|
+
return this.displayMode === 'dialog';
|
|
3347
|
+
}
|
|
3348
|
+
get isDrawerMode() {
|
|
3349
|
+
return this.displayMode === 'drawer';
|
|
3350
|
+
}
|
|
3351
|
+
// Dialog configuration with defaults
|
|
3352
|
+
get dialogHeader() {
|
|
3353
|
+
return this.dialogConfig.header || this.translationService.translate('crmx.business.form');
|
|
3354
|
+
}
|
|
3355
|
+
get dialogWidth() {
|
|
3356
|
+
return this.dialogConfig.width || '600px';
|
|
3357
|
+
}
|
|
3358
|
+
get dialogDraggable() {
|
|
3359
|
+
return this.dialogConfig.draggable !== undefined ? this.dialogConfig.draggable : false;
|
|
3360
|
+
}
|
|
3361
|
+
get dialogResizable() {
|
|
3362
|
+
return this.dialogConfig.resizable !== undefined ? this.dialogConfig.resizable : false;
|
|
3363
|
+
}
|
|
3364
|
+
get dialogMaximizable() {
|
|
3365
|
+
return this.dialogConfig.maximizable !== undefined ? this.dialogConfig.maximizable : false;
|
|
3366
|
+
}
|
|
3367
|
+
get dialogCloseOnEscape() {
|
|
3368
|
+
return this.dialogConfig.closeOnEscape !== undefined ? this.dialogConfig.closeOnEscape : true;
|
|
3369
|
+
}
|
|
3370
|
+
get dialogDismissableMask() {
|
|
3371
|
+
return this.dialogConfig.dismissableMask !== undefined ? this.dialogConfig.dismissableMask : false;
|
|
3372
|
+
}
|
|
3373
|
+
// Drawer configuration with defaults
|
|
3374
|
+
get drawerHeader() {
|
|
3375
|
+
return this.drawerConfig.header || this.translationService.translate('crmx.business.form');
|
|
3376
|
+
}
|
|
3377
|
+
get drawerPosition() {
|
|
3378
|
+
return this.drawerConfig.position || 'right';
|
|
3379
|
+
}
|
|
3380
|
+
get drawerShowCloseIcon() {
|
|
3381
|
+
return this.drawerConfig.showCloseIcon !== undefined ? this.drawerConfig.showCloseIcon : true;
|
|
3382
|
+
}
|
|
3383
|
+
get drawerCloseOnEscape() {
|
|
3384
|
+
return this.drawerConfig.closeOnEscape !== undefined ? this.drawerConfig.closeOnEscape : true;
|
|
3385
|
+
}
|
|
3386
|
+
get drawerDismissable() {
|
|
3387
|
+
return this.drawerConfig.dismissable !== undefined ? this.drawerConfig.dismissable : true;
|
|
3388
|
+
}
|
|
3389
|
+
get drawerStyle() {
|
|
3390
|
+
const position = this.drawerPosition;
|
|
3391
|
+
const isHorizontal = position === 'left' || position === 'right';
|
|
3392
|
+
// Se tem customWidth, usa ela
|
|
3393
|
+
if (this.drawerConfig.customWidth) {
|
|
3394
|
+
return isHorizontal
|
|
3395
|
+
? { width: this.drawerConfig.customWidth }
|
|
3396
|
+
: { height: this.drawerConfig.customWidth };
|
|
3397
|
+
}
|
|
3398
|
+
// Usa o tamanho padronizado
|
|
3399
|
+
const size = this.drawerConfig.size || 'medium';
|
|
3400
|
+
const sizeMap = {
|
|
3401
|
+
'small': isHorizontal ? '25%' : '25vh',
|
|
3402
|
+
'medium': isHorizontal ? '40%' : '40vh',
|
|
3403
|
+
'large': isHorizontal ? '60%' : '60vh',
|
|
3404
|
+
'xlarge': isHorizontal ? '80%' : '80vh',
|
|
3405
|
+
'full': isHorizontal ? '100%' : '100vh'
|
|
3406
|
+
};
|
|
3407
|
+
const sizeValue = sizeMap[size];
|
|
3408
|
+
return isHorizontal
|
|
3409
|
+
? { width: sizeValue }
|
|
3410
|
+
: { height: sizeValue };
|
|
3411
|
+
}
|
|
3412
|
+
get submitLabel() {
|
|
3413
|
+
return this.submitButtonLabel || this.translationService.translate('crmx.business.save');
|
|
3414
|
+
}
|
|
3415
|
+
get cancelLabel() {
|
|
3416
|
+
return this.cancelButtonLabel || this.translationService.translate('crmx.business.cancel');
|
|
3417
|
+
}
|
|
3418
|
+
constructor(fb, translationService) {
|
|
3419
|
+
this.fb = fb;
|
|
3420
|
+
this.translationService = translationService;
|
|
3421
|
+
}
|
|
3422
|
+
ngOnInit() {
|
|
3423
|
+
this.initForm();
|
|
3424
|
+
this.setupConditionalFieldsWatcher();
|
|
3425
|
+
this.formReady.emit(this.form);
|
|
3426
|
+
}
|
|
3427
|
+
ngOnChanges(changes) {
|
|
3428
|
+
if (changes['entityData'] && this.form) {
|
|
3429
|
+
if (this.entityData) {
|
|
3430
|
+
this.loadEntityData();
|
|
3431
|
+
}
|
|
3432
|
+
else {
|
|
3433
|
+
this.resetForm();
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
// Reset loading and form when dialog/drawer is opened
|
|
3437
|
+
if (changes['visible'] && changes['visible'].currentValue === true) {
|
|
3438
|
+
this.loading = false;
|
|
3439
|
+
// If opening without entityData (new record), reset form
|
|
3440
|
+
if (!this.entityData && this.form) {
|
|
3441
|
+
this.resetForm();
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
initForm() {
|
|
3446
|
+
const formControls = {};
|
|
3447
|
+
// Processa todos os campos (diretos + das seções)
|
|
3448
|
+
this.allFields.forEach(field => {
|
|
3449
|
+
const defaultValue = this.getDefaultValue(field);
|
|
3450
|
+
const validators = this.mode === 'filter' ? [] : (field.validators || []);
|
|
3451
|
+
const control = this.fb.control({ value: defaultValue, disabled: field.disabled || false }, validators);
|
|
3452
|
+
formControls[field.field] = control;
|
|
3453
|
+
});
|
|
3454
|
+
this.form = this.fb.group(formControls);
|
|
3455
|
+
this.applyInitialDisabledState();
|
|
3456
|
+
}
|
|
3457
|
+
getDefaultValue(field) {
|
|
3458
|
+
if (field.defaultValue !== undefined) {
|
|
3459
|
+
return field.defaultValue;
|
|
3460
|
+
}
|
|
3461
|
+
switch (field.type) {
|
|
3462
|
+
case 'number':
|
|
3463
|
+
case 'date':
|
|
3464
|
+
return null;
|
|
3465
|
+
case 'dropdown':
|
|
3466
|
+
case 'enum':
|
|
3467
|
+
return this.mode === 'filter' ? null : '';
|
|
3468
|
+
default:
|
|
3469
|
+
return '';
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
applyInitialDisabledState() {
|
|
3473
|
+
this.allFields.forEach(field => {
|
|
3474
|
+
if (field.disabledWhen) {
|
|
3475
|
+
const control = this.form.get(field.field);
|
|
3476
|
+
if (control && this.isFieldDisabled(field)) {
|
|
3477
|
+
control.disable();
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
loadEntityData() {
|
|
3483
|
+
if (this.entityData && this.form) {
|
|
3484
|
+
const processedData = this.processEntityData(this.entityData);
|
|
3485
|
+
this.form.patchValue(processedData);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
processEntityData(entity) {
|
|
3489
|
+
const processedData = { ...entity };
|
|
3490
|
+
this.allFields.forEach(field => {
|
|
3491
|
+
if (field.type === 'lookup' && processedData[field.field]) {
|
|
3492
|
+
const lookupValue = processedData[field.field];
|
|
3493
|
+
// Keep the full object if it has display data, otherwise extract ID
|
|
3494
|
+
if (typeof lookupValue === 'object' && lookupValue.id) {
|
|
3495
|
+
const hasDisplayData = field.lookupDisplayFields
|
|
3496
|
+
? field.lookupDisplayFields.some(f => this.getNestedValue(lookupValue, f))
|
|
3497
|
+
: lookupValue[field.lookupDisplayField || 'name'];
|
|
3498
|
+
if (hasDisplayData) {
|
|
3499
|
+
// Keep the full object to avoid unnecessary API calls
|
|
3500
|
+
processedData[field.field] = lookupValue;
|
|
3501
|
+
}
|
|
3502
|
+
else {
|
|
3503
|
+
// Only has ID, extract it
|
|
3504
|
+
processedData[field.field] = lookupValue.id;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
});
|
|
3509
|
+
return processedData;
|
|
3510
|
+
}
|
|
3511
|
+
getNestedValue(obj, path) {
|
|
3512
|
+
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
3513
|
+
}
|
|
3514
|
+
resetForm() {
|
|
3515
|
+
if (this.form) {
|
|
3516
|
+
const resetValues = {};
|
|
3517
|
+
this.allFields.forEach(field => {
|
|
3518
|
+
resetValues[field.field] = this.getDefaultValue(field);
|
|
3519
|
+
});
|
|
3520
|
+
this.form.reset(resetValues);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
processFilterValue(formValue) {
|
|
3524
|
+
const processedValue = { ...formValue };
|
|
3525
|
+
this.allFields.forEach(field => {
|
|
3526
|
+
const fieldValue = processedValue[field.field];
|
|
3527
|
+
if (field.type === 'lookup') {
|
|
3528
|
+
if (fieldValue && typeof fieldValue === 'object' && fieldValue.id) {
|
|
3529
|
+
processedValue[field.field] = fieldValue;
|
|
3530
|
+
}
|
|
3531
|
+
else if (fieldValue && typeof fieldValue === 'string') {
|
|
3532
|
+
processedValue[field.field] = { id: fieldValue };
|
|
3533
|
+
}
|
|
3534
|
+
else {
|
|
3535
|
+
processedValue[field.field] = null;
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
});
|
|
3539
|
+
return processedValue;
|
|
3540
|
+
}
|
|
3541
|
+
processFormValue(formValue) {
|
|
3542
|
+
const processedValue = { ...formValue };
|
|
3543
|
+
if (this.entityData?.id) {
|
|
3544
|
+
processedValue.id = this.entityData.id;
|
|
3545
|
+
}
|
|
3546
|
+
this.allFields.forEach(field => {
|
|
3547
|
+
const fieldValue = processedValue[field.field];
|
|
3548
|
+
if (field.type === 'lookup') {
|
|
3549
|
+
// If it's an object, extract the ID
|
|
3550
|
+
if (fieldValue && typeof fieldValue === 'object') {
|
|
3551
|
+
processedValue[field.field] = fieldValue.id || fieldValue[field.lookupValueField || 'id'] || null;
|
|
3552
|
+
}
|
|
3553
|
+
else if (fieldValue && fieldValue !== '') {
|
|
3554
|
+
// If it's already an ID, keep it
|
|
3555
|
+
processedValue[field.field] = { id: fieldValue };
|
|
3556
|
+
}
|
|
3557
|
+
else {
|
|
3558
|
+
processedValue[field.field] = null;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
else if (field.type === 'date') {
|
|
3562
|
+
if (fieldValue instanceof Date) {
|
|
3563
|
+
processedValue[field.field] = fieldValue.toISOString().split('T')[0];
|
|
3564
|
+
}
|
|
3565
|
+
else if (!fieldValue) {
|
|
3566
|
+
processedValue[field.field] = null;
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
else if (['text', 'textarea', 'number', 'enum', 'dropdown'].includes(field.type)) {
|
|
3570
|
+
if (fieldValue === '' || fieldValue === undefined) {
|
|
3571
|
+
processedValue[field.field] = null;
|
|
3572
|
+
}
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3575
|
+
return processedValue;
|
|
3576
|
+
}
|
|
3577
|
+
getFieldGridClass(field) {
|
|
3578
|
+
const classes = [];
|
|
3579
|
+
// Colspan base (padrão: 6)
|
|
3580
|
+
let baseColspan = field.colspan || 6;
|
|
3581
|
+
// Verifica condições de colspan dinâmico
|
|
3582
|
+
if (field.colspanWhen && field.colspanWhen.length > 0) {
|
|
3583
|
+
for (const condition of field.colspanWhen) {
|
|
3584
|
+
const dependentFieldValue = this.form.get(condition.field)?.value;
|
|
3585
|
+
if (dependentFieldValue === condition.value) {
|
|
3586
|
+
baseColspan = condition.colspan;
|
|
3587
|
+
break;
|
|
3588
|
+
}
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
// Adiciona classe base
|
|
3592
|
+
classes.push(`col-${baseColspan}`);
|
|
3593
|
+
// Suporta tanto o objeto size quanto as propriedades antigas (para compatibilidade)
|
|
3594
|
+
const sizeXs = field.size?.xs ?? field.colspanXs;
|
|
3595
|
+
const sizeSm = field.size?.sm ?? field.colspanSm;
|
|
3596
|
+
const sizeMd = field.size?.md ?? field.colspanMd;
|
|
3597
|
+
const sizeLg = field.size?.lg ?? field.colspanLg;
|
|
3598
|
+
const sizeXl = field.size?.xl ?? field.colspanXl;
|
|
3599
|
+
// Adiciona classes responsivas se definidas
|
|
3600
|
+
if (sizeXs !== undefined) {
|
|
3601
|
+
classes.push(`col-xs-${sizeXs}`);
|
|
3602
|
+
}
|
|
3603
|
+
if (sizeSm !== undefined) {
|
|
3604
|
+
classes.push(`col-sm-${sizeSm}`);
|
|
3605
|
+
}
|
|
3606
|
+
if (sizeMd !== undefined) {
|
|
3607
|
+
classes.push(`col-md-${sizeMd}`);
|
|
3608
|
+
}
|
|
3609
|
+
if (sizeLg !== undefined) {
|
|
3610
|
+
classes.push(`col-lg-${sizeLg}`);
|
|
3611
|
+
}
|
|
3612
|
+
if (sizeXl !== undefined) {
|
|
3613
|
+
classes.push(`col-xl-${sizeXl}`);
|
|
3614
|
+
}
|
|
3615
|
+
return classes.join(' ');
|
|
3616
|
+
}
|
|
3617
|
+
isFieldVisible(field) {
|
|
3618
|
+
if (this.mode === 'filter') {
|
|
3619
|
+
return true;
|
|
3620
|
+
}
|
|
3621
|
+
if (!field.visibleWhen) {
|
|
3622
|
+
return true;
|
|
3623
|
+
}
|
|
3624
|
+
const dependentFieldValue = this.form.get(field.visibleWhen.field)?.value;
|
|
3625
|
+
return dependentFieldValue === field.visibleWhen.value;
|
|
3626
|
+
}
|
|
3627
|
+
isFieldDisabled(field) {
|
|
3628
|
+
if (field.disabled) {
|
|
3629
|
+
return true;
|
|
3630
|
+
}
|
|
3631
|
+
if (!('disabledWhen' in field) || !field.disabledWhen) {
|
|
3632
|
+
return false;
|
|
3633
|
+
}
|
|
3634
|
+
const dependentFieldValue = this.form.get(field.disabledWhen.field)?.value;
|
|
3635
|
+
if (field.disabledWhen.value === null) {
|
|
3636
|
+
return !dependentFieldValue;
|
|
3637
|
+
}
|
|
3638
|
+
return dependentFieldValue === field.disabledWhen.value;
|
|
3639
|
+
}
|
|
3640
|
+
setupConditionalFieldsWatcher() {
|
|
3641
|
+
const visibleDependentFields = this.allFields.filter(field => field.visibleWhen);
|
|
3642
|
+
const visibleWatchedFields = [...new Set(visibleDependentFields.map(field => field.visibleWhen.field))];
|
|
3643
|
+
const disabledDependentFields = this.allFields.filter(field => field.disabledWhen);
|
|
3644
|
+
const disabledWatchedFields = [...new Set(disabledDependentFields.map(field => field.disabledWhen.field))];
|
|
3645
|
+
const allWatchedFields = [...new Set([...visibleWatchedFields, ...disabledWatchedFields])];
|
|
3646
|
+
allWatchedFields.forEach(fieldName => {
|
|
3647
|
+
const control = this.form.get(fieldName);
|
|
3648
|
+
if (control) {
|
|
3649
|
+
control.valueChanges.subscribe(() => {
|
|
3650
|
+
this.clearHiddenFields();
|
|
3651
|
+
this.updateDisabledFields();
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
updateDisabledFields() {
|
|
3657
|
+
this.allFields.forEach(field => {
|
|
3658
|
+
if (field.disabledWhen) {
|
|
3659
|
+
const control = this.form.get(field.field);
|
|
3660
|
+
if (control) {
|
|
3661
|
+
const shouldBeDisabled = this.isFieldDisabled(field);
|
|
3662
|
+
if (shouldBeDisabled && control.enabled) {
|
|
3663
|
+
control.setValue(null);
|
|
3664
|
+
control.disable();
|
|
3665
|
+
}
|
|
3666
|
+
else if (!shouldBeDisabled && control.disabled && !field.disabled) {
|
|
3667
|
+
control.enable();
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
});
|
|
3672
|
+
}
|
|
3673
|
+
clearHiddenFields() {
|
|
3674
|
+
this.allFields.forEach(field => {
|
|
3675
|
+
if (field.visibleWhen && !this.isFieldVisible(field)) {
|
|
3676
|
+
const control = this.form.get(field.field);
|
|
3677
|
+
if (control) {
|
|
3678
|
+
const resetValue = this.getDefaultValue(field);
|
|
3679
|
+
control.setValue(resetValue);
|
|
3680
|
+
control.markAsUntouched();
|
|
3681
|
+
control.markAsPristine();
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
});
|
|
3685
|
+
}
|
|
3686
|
+
getForm() {
|
|
3687
|
+
return this.form;
|
|
3688
|
+
}
|
|
3689
|
+
clearFilters() {
|
|
3690
|
+
if (this.mode === 'filter') {
|
|
3691
|
+
this.resetForm();
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
getTypePersonValue() {
|
|
3695
|
+
const typePersonValue = this.form?.get('typePerson')?.value;
|
|
3696
|
+
return typePersonValue || 'JURIDICAL_PERSON';
|
|
3697
|
+
}
|
|
3698
|
+
// Display Mode Control Methods
|
|
3699
|
+
open(entity) {
|
|
3700
|
+
if (entity) {
|
|
3701
|
+
this.entityData = entity;
|
|
3702
|
+
}
|
|
3703
|
+
else {
|
|
3704
|
+
this.entityData = null;
|
|
3705
|
+
this.resetForm(); // Reset form when opening for new entity
|
|
3706
|
+
}
|
|
3707
|
+
this.loading = false; // Reset loading state
|
|
3708
|
+
this.visible = true;
|
|
3709
|
+
this.visibleChange.emit(true);
|
|
3710
|
+
}
|
|
3711
|
+
close() {
|
|
3712
|
+
this.loading = false; // Reset loading state
|
|
3713
|
+
this.visible = false;
|
|
3714
|
+
this.visibleChange.emit(false);
|
|
3715
|
+
this.resetForm(); // Reset form when closing
|
|
3716
|
+
this.onCancel.emit();
|
|
3717
|
+
}
|
|
3718
|
+
handleCancel() {
|
|
3719
|
+
this.close();
|
|
3720
|
+
}
|
|
3721
|
+
handleSubmit() {
|
|
3722
|
+
if (this.mode === 'form' && this.form.invalid) {
|
|
3723
|
+
this.form.markAllAsTouched();
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
this.loading = true;
|
|
3727
|
+
const formValue = this.mode === 'filter'
|
|
3728
|
+
? this.processFilterValue(this.form.value)
|
|
3729
|
+
: this.processFormValue(this.form.value);
|
|
3730
|
+
this.formSubmit.emit(formValue);
|
|
3731
|
+
}
|
|
3732
|
+
// Inline Edit Mode Methods
|
|
3733
|
+
startEditField(fieldName) {
|
|
3734
|
+
if (this.isInlineEditMode) {
|
|
3735
|
+
this.editingField = fieldName;
|
|
3736
|
+
// Ensure the field has the current value from entityData
|
|
3737
|
+
if (this.entityData) {
|
|
3738
|
+
const control = this.form.get(fieldName);
|
|
3739
|
+
if (control) {
|
|
3740
|
+
const field = this.allFields.find(f => f.field === fieldName);
|
|
3741
|
+
const value = this.entityData[fieldName];
|
|
3742
|
+
// For lookup fields in inline-edit mode, keep the full object
|
|
3743
|
+
// This prevents unnecessary API calls since we already have the data
|
|
3744
|
+
if (field?.type === 'lookup' && value && typeof value === 'object') {
|
|
3745
|
+
// Keep the full object - the lookup component will use it
|
|
3746
|
+
control.setValue(value);
|
|
3747
|
+
}
|
|
3748
|
+
else {
|
|
3749
|
+
control.setValue(value);
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
isFieldEditing(fieldName) {
|
|
3756
|
+
return this.editingField === fieldName;
|
|
3757
|
+
}
|
|
3758
|
+
saveInlineField(fieldName) {
|
|
3759
|
+
if (!this.isInlineEditMode)
|
|
3760
|
+
return;
|
|
3761
|
+
const control = this.form.get(fieldName);
|
|
3762
|
+
if (!control || control.invalid) {
|
|
3763
|
+
control?.markAsTouched();
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
this.savingField = true;
|
|
3767
|
+
let fieldValue = control.value;
|
|
3768
|
+
// For lookup fields, extract the ID if it's an object
|
|
3769
|
+
const field = this.allFields.find(f => f.field === fieldName);
|
|
3770
|
+
if (field?.type === 'lookup' && fieldValue && typeof fieldValue === 'object') {
|
|
3771
|
+
fieldValue = fieldValue.id || fieldValue[field.lookupValueField || 'id'];
|
|
3772
|
+
}
|
|
3773
|
+
this.fieldSave.emit({ field: fieldName, value: fieldValue });
|
|
3774
|
+
}
|
|
3775
|
+
cancelInlineEdit() {
|
|
3776
|
+
if (this.isInlineEditMode && this.editingField) {
|
|
3777
|
+
const control = this.form.get(this.editingField);
|
|
3778
|
+
if (control && this.entityData) {
|
|
3779
|
+
// Restore original value
|
|
3780
|
+
const originalValue = this.entityData[this.editingField];
|
|
3781
|
+
control.setValue(originalValue);
|
|
3782
|
+
control.markAsPristine();
|
|
3783
|
+
control.markAsUntouched();
|
|
3784
|
+
}
|
|
3785
|
+
this.editingField = null;
|
|
3786
|
+
this.savingField = false;
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
// Method to be called by parent after save completes
|
|
3790
|
+
completeInlineEdit() {
|
|
3791
|
+
this.editingField = null;
|
|
3792
|
+
this.savingField = false;
|
|
3793
|
+
}
|
|
3794
|
+
getFieldDisplayValue(fieldName) {
|
|
3795
|
+
if (!this.entityData)
|
|
3796
|
+
return '';
|
|
3797
|
+
const field = this.allFields.find(f => f.field === fieldName);
|
|
3798
|
+
const value = this.entityData[fieldName];
|
|
3799
|
+
if (!value)
|
|
3800
|
+
return '';
|
|
3801
|
+
// For lookup fields, return the display name
|
|
3802
|
+
if (field?.type === 'lookup' && typeof value === 'object' && value.name) {
|
|
3803
|
+
return value.name;
|
|
3804
|
+
}
|
|
3805
|
+
// For number fields, format appropriately
|
|
3806
|
+
if (field?.type === 'number' && typeof value === 'number') {
|
|
3807
|
+
if (field.numberMode === 'currency') {
|
|
3808
|
+
return new Intl.NumberFormat(field.locale || 'pt-BR', {
|
|
3809
|
+
style: 'currency',
|
|
3810
|
+
currency: field.currency || 'BRL'
|
|
3811
|
+
}).format(value);
|
|
3812
|
+
}
|
|
3813
|
+
return new Intl.NumberFormat(field.locale || 'pt-BR', {
|
|
3814
|
+
minimumFractionDigits: field.minFractionDigits || 0,
|
|
3815
|
+
maximumFractionDigits: field.maxFractionDigits || 2
|
|
3816
|
+
}).format(value);
|
|
3817
|
+
}
|
|
3818
|
+
// For date fields, format as date
|
|
3819
|
+
if (field?.type === 'date' && value) {
|
|
3820
|
+
const date = typeof value === 'string' ? new Date(value) : value;
|
|
3821
|
+
return date.toLocaleDateString('pt-BR');
|
|
3822
|
+
}
|
|
3823
|
+
return String(value);
|
|
3824
|
+
}
|
|
3825
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFormComponent, deps: [{ token: i2$1.FormBuilder }, { token: TranslationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3826
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: DynamicFormComponent, isStandalone: true, selector: "sia-dynamic-form", inputs: { formFields: "formFields", formSections: "formSections", fixedFilters: "fixedFilters", filterSections: "filterSections", entityData: "entityData", mode: "mode", displayMode: "displayMode", visible: "visible", dialogConfig: "dialogConfig", drawerConfig: "drawerConfig", showSubmitButton: "showSubmitButton", showCancelButton: "showCancelButton", submitButtonLabel: "submitButtonLabel", cancelButtonLabel: "cancelButtonLabel", submitButtonIcon: "submitButtonIcon", cancelButtonIcon: "cancelButtonIcon" }, outputs: { formReady: "formReady", formSubmit: "formSubmit", visibleChange: "visibleChange", onCancel: "onCancel", fieldSave: "fieldSave" }, usesOnChanges: true, ngImport: i0, template: "<!-- Modo Inline (padr\u00E3o) -->\n<ng-container *ngIf=\"isInlineMode\">\n <ng-container *ngTemplateOutlet=\"formContent\"></ng-container>\n</ng-container>\n\n<!-- Modo Dialog -->\n<p-dialog\n *ngIf=\"isDialogMode\"\n [(visible)]=\"visible\"\n [header]=\"dialogHeader | translate\"\n [modal]=\"true\"\n [style]=\"{ width: dialogWidth }\"\n [draggable]=\"dialogDraggable\"\n [resizable]=\"dialogResizable\"\n [maximizable]=\"dialogMaximizable\"\n [closeOnEscape]=\"dialogCloseOnEscape\"\n [dismissableMask]=\"dialogDismissableMask\"\n (onHide)=\"close()\">\n \n <ng-container *ngTemplateOutlet=\"formContent\"></ng-container>\n \n <ng-template pTemplate=\"footer\" *ngIf=\"showSubmitButton || showCancelButton\">\n <div class=\"dialog-footer\">\n <p-button\n *ngIf=\"showCancelButton\"\n [label]=\"cancelLabel | translate\"\n [icon]=\"cancelButtonIcon\"\n severity=\"secondary\"\n (onClick)=\"handleCancel()\"\n [disabled]=\"loading\">\n </p-button>\n <p-button\n *ngIf=\"showSubmitButton\"\n [label]=\"submitLabel | translate\"\n [icon]=\"submitButtonIcon\"\n (onClick)=\"handleSubmit()\"\n [loading]=\"loading\"\n [disabled]=\"form.invalid\">\n </p-button>\n </div>\n </ng-template>\n</p-dialog>\n\n<!-- Modo Drawer -->\n<p-drawer\n *ngIf=\"isDrawerMode\"\n [(visible)]=\"visible\"\n [header]=\"drawerHeader | translate\"\n [position]=\"drawerPosition\"\n [style]=\"drawerStyle\"\n [showCloseIcon]=\"drawerShowCloseIcon\"\n [closeOnEscape]=\"drawerCloseOnEscape\"\n [modal]=\"drawerDismissable\"\n (onHide)=\"close()\">\n \n <ng-container *ngTemplateOutlet=\"formContent\"></ng-container>\n \n <ng-template pTemplate=\"footer\" *ngIf=\"showSubmitButton || showCancelButton\">\n <div class=\"drawer-footer\">\n <p-button\n *ngIf=\"showCancelButton\"\n [label]=\"cancelLabel | translate\"\n [icon]=\"cancelButtonIcon\"\n severity=\"secondary\"\n (onClick)=\"handleCancel()\"\n [disabled]=\"loading\">\n </p-button>\n <p-button\n *ngIf=\"showSubmitButton\"\n [label]=\"submitLabel | translate\"\n [icon]=\"submitButtonIcon\"\n (onClick)=\"handleSubmit()\"\n [loading]=\"loading\"\n [disabled]=\"form.invalid\">\n </p-button>\n </div>\n </ng-template>\n</p-drawer>\n\n<!-- Template do Formul\u00E1rio (reutiliz\u00E1vel) -->\n<ng-template #formContent>\n <form [formGroup]=\"form\" class=\"dynamic-form\">\n \n <!-- Filtros Fixos (apenas no modo filter) -->\n <div *ngIf=\"mode === 'filter' && hasFixedFilters\" class=\"fixed-filters-section\">\n <div class=\"grid\">\n <ng-container *ngFor=\"let field of fixedFilters\">\n <div *ngIf=\"isFieldVisible(field)\" [ngClass]=\"getFieldGridClass(field)\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n </div>\n\n <!-- Campos Diretos (sem se\u00E7\u00E3o) -->\n <div *ngIf=\"hasFields\">\n <!-- Modo Inline Edit -->\n <div *ngIf=\"isInlineEditMode\" class=\"inline-edit-fields\">\n <ng-container *ngFor=\"let field of formFields\">\n <div *ngIf=\"isFieldVisible(field)\" class=\"inline-edit-field\">\n <ng-container *ngTemplateOutlet=\"inlineEditFieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n\n <!-- Modo Form Normal -->\n <div *ngIf=\"!isInlineEditMode\" class=\"grid\">\n <ng-container *ngFor=\"let field of formFields\">\n <div *ngIf=\"isFieldVisible(field)\" [ngClass]=\"getFieldGridClass(field)\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n </div>\n\n <!-- Se\u00E7\u00F5es de Campos (modo form) -->\n <div *ngIf=\"hasSections\" class=\"sections-container\">\n <p-panel \n *ngFor=\"let section of formSections\"\n [toggleable]=\"section.toggleable !== false\"\n [collapsed]=\"section.collapsed || false\"\n [styleClass]=\"'section-panel'\">\n \n <ng-template pTemplate=\"header\">\n <div class=\"section-header\">\n <i *ngIf=\"section.icon\" [class]=\"section.icon + ' section-icon'\"></i>\n <span class=\"section-title\">{{ section.title }}</span>\n </div>\n </ng-template>\n\n <!-- Modo Inline Edit -->\n <div *ngIf=\"isInlineEditMode\" class=\"inline-edit-fields\">\n <ng-container *ngFor=\"let field of section.fields\">\n <div *ngIf=\"isFieldVisible(field)\" class=\"inline-edit-field\">\n <ng-container *ngTemplateOutlet=\"inlineEditFieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n\n <!-- Modo Form Normal -->\n <div *ngIf=\"!isInlineEditMode\" class=\"grid\">\n <ng-container *ngFor=\"let field of section.fields\">\n <div *ngIf=\"isFieldVisible(field)\" [ngClass]=\"getFieldGridClass(field)\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n </p-panel>\n </div>\n\n <!-- Se\u00E7\u00F5es de Filtros (apenas no modo filter) -->\n <div *ngIf=\"mode === 'filter' && hasFilterSections\" class=\"filter-sections-container\">\n <p-panel \n *ngFor=\"let section of filterSections\"\n [header]=\"section.title\"\n [toggleable]=\"true\"\n [collapsed]=\"true\">\n\n <div class=\"grid\">\n <ng-container *ngFor=\"let field of section.fields\">\n <div *ngIf=\"isFieldVisible(field)\" [ngClass]=\"getFieldGridClass(field)\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n </p-panel>\n </div>\n\n <!-- Bot\u00F5es inline (apenas para modo inline) -->\n <div *ngIf=\"isInlineMode && (showSubmitButton || showCancelButton)\" class=\"form-actions\">\n <p-button\n *ngIf=\"showCancelButton\"\n [label]=\"cancelLabel | translate\"\n [icon]=\"cancelButtonIcon\"\n severity=\"secondary\"\n (onClick)=\"handleCancel()\"\n [disabled]=\"loading\">\n </p-button>\n <p-button\n *ngIf=\"showSubmitButton\"\n [label]=\"submitLabel | translate\"\n [icon]=\"submitButtonIcon\"\n (onClick)=\"handleSubmit()\"\n [loading]=\"loading\"\n [disabled]=\"form.invalid\">\n </p-button>\n </div>\n\n </form>\n</ng-template>\n\n<!-- Template de Campo (reutiliz\u00E1vel) -->\n<ng-template #fieldTemplate let-field=\"field\">\n <!-- Text Field -->\n <sia-dynamic-field-text\n *ngIf=\"field.type === 'text'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-text>\n\n <!-- Textarea Field -->\n <sia-dynamic-field-textarea\n *ngIf=\"field.type === 'textarea'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-textarea>\n\n <!-- Number Field -->\n <sia-dynamic-field-number\n *ngIf=\"field.type === 'number'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-number>\n\n <!-- Date Field -->\n <sia-dynamic-field-date\n *ngIf=\"field.type === 'date'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-date>\n\n <!-- Dropdown/Enum Field -->\n <sia-dynamic-field-dropdown\n *ngIf=\"field.type === 'dropdown' || field.type === 'enum'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-dropdown>\n\n <!-- Lookup Field -->\n <sia-dynamic-field-lookup\n *ngIf=\"field.type === 'lookup'\"\n [field]=\"field\"\n [form]=\"form\"\n [formGroup]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-lookup>\n</ng-template>\n\n\n<!-- Template de Campo Inline Edit -->\n<ng-template #inlineEditFieldTemplate let-field=\"field\">\n <div class=\"edit-inline-field\">\n <label class=\"field-label\">{{ field.label | translate }}</label>\n \n <!-- Display Mode -->\n <div class=\"field-display\" \n *ngIf=\"!isFieldEditing(field.field)\" \n (click)=\"startEditField(field.field)\"\n [class.disabled]=\"field.disabled\">\n <span class=\"display-value\" \n [attr.data-placeholder]=\"field.placeholder || ('crmx.business.not_informed' | translate)\">\n {{ getFieldDisplayValue(field.field) }}\n </span>\n <i class=\"pi pi-pencil edit-icon\" *ngIf=\"!field.disabled\"></i>\n </div>\n \n <!-- Edit Mode -->\n <div class=\"field-edit\" *ngIf=\"isFieldEditing(field.field)\">\n <div class=\"input-container\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n \n <!-- Action Buttons -->\n <div class=\"edit-actions\">\n <p-button \n type=\"button\" \n icon=\"pi pi-check\" \n [rounded]=\"true\"\n severity=\"primary\"\n (onClick)=\"saveInlineField(field.field)\" \n [loading]=\"savingField\"\n [disabled]=\"form.get(field.field)?.invalid\">\n </p-button>\n <p-button \n type=\"button\" \n icon=\"pi pi-times\" \n [rounded]=\"true\"\n severity=\"secondary\"\n (onClick)=\"cancelInlineEdit()\"\n [disabled]=\"savingField\">\n </p-button>\n </div>\n \n <!-- Error Message -->\n <div class=\"field-error\" *ngIf=\"form.get(field.field)?.invalid && form.get(field.field)?.touched\">\n <small class=\"p-error\">\n {{ form.get(field.field)?.errors?.['required'] ? ('crmx.business.required_field' | translate) : '' }}\n </small>\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [".dynamic-form{width:100%}.dynamic-form .grid{display:grid;grid-template-columns:repeat(12,1fr);gap:1rem}.dynamic-form .grid>div.col-1{grid-column:span 1}.dynamic-form .grid>div.col-2{grid-column:span 2}.dynamic-form .grid>div.col-3{grid-column:span 3}.dynamic-form .grid>div.col-4{grid-column:span 4}.dynamic-form .grid>div.col-5{grid-column:span 5}.dynamic-form .grid>div.col-6{grid-column:span 6}.dynamic-form .grid>div.col-7{grid-column:span 7}.dynamic-form .grid>div.col-8{grid-column:span 8}.dynamic-form .grid>div.col-9{grid-column:span 9}.dynamic-form .grid>div.col-10{grid-column:span 10}.dynamic-form .grid>div.col-11{grid-column:span 11}.dynamic-form .grid>div.col-12{grid-column:span 12}@media (max-width: 575.98px){.dynamic-form .grid>div.col-xs-1{grid-column:span 1}.dynamic-form .grid>div.col-xs-2{grid-column:span 2}.dynamic-form .grid>div.col-xs-3{grid-column:span 3}.dynamic-form .grid>div.col-xs-4{grid-column:span 4}.dynamic-form .grid>div.col-xs-5{grid-column:span 5}.dynamic-form .grid>div.col-xs-6{grid-column:span 6}.dynamic-form .grid>div.col-xs-7{grid-column:span 7}.dynamic-form .grid>div.col-xs-8{grid-column:span 8}.dynamic-form .grid>div.col-xs-9{grid-column:span 9}.dynamic-form .grid>div.col-xs-10{grid-column:span 10}.dynamic-form .grid>div.col-xs-11{grid-column:span 11}.dynamic-form .grid>div.col-xs-12{grid-column:span 12}}@media (min-width: 576px){.dynamic-form .grid>div.col-sm-1{grid-column:span 1}.dynamic-form .grid>div.col-sm-2{grid-column:span 2}.dynamic-form .grid>div.col-sm-3{grid-column:span 3}.dynamic-form .grid>div.col-sm-4{grid-column:span 4}.dynamic-form .grid>div.col-sm-5{grid-column:span 5}.dynamic-form .grid>div.col-sm-6{grid-column:span 6}.dynamic-form .grid>div.col-sm-7{grid-column:span 7}.dynamic-form .grid>div.col-sm-8{grid-column:span 8}.dynamic-form .grid>div.col-sm-9{grid-column:span 9}.dynamic-form .grid>div.col-sm-10{grid-column:span 10}.dynamic-form .grid>div.col-sm-11{grid-column:span 11}.dynamic-form .grid>div.col-sm-12{grid-column:span 12}}@media (min-width: 768px){.dynamic-form .grid>div.col-md-1{grid-column:span 1}.dynamic-form .grid>div.col-md-2{grid-column:span 2}.dynamic-form .grid>div.col-md-3{grid-column:span 3}.dynamic-form .grid>div.col-md-4{grid-column:span 4}.dynamic-form .grid>div.col-md-5{grid-column:span 5}.dynamic-form .grid>div.col-md-6{grid-column:span 6}.dynamic-form .grid>div.col-md-7{grid-column:span 7}.dynamic-form .grid>div.col-md-8{grid-column:span 8}.dynamic-form .grid>div.col-md-9{grid-column:span 9}.dynamic-form .grid>div.col-md-10{grid-column:span 10}.dynamic-form .grid>div.col-md-11{grid-column:span 11}.dynamic-form .grid>div.col-md-12{grid-column:span 12}}@media (min-width: 992px){.dynamic-form .grid>div.col-lg-1{grid-column:span 1}.dynamic-form .grid>div.col-lg-2{grid-column:span 2}.dynamic-form .grid>div.col-lg-3{grid-column:span 3}.dynamic-form .grid>div.col-lg-4{grid-column:span 4}.dynamic-form .grid>div.col-lg-5{grid-column:span 5}.dynamic-form .grid>div.col-lg-6{grid-column:span 6}.dynamic-form .grid>div.col-lg-7{grid-column:span 7}.dynamic-form .grid>div.col-lg-8{grid-column:span 8}.dynamic-form .grid>div.col-lg-9{grid-column:span 9}.dynamic-form .grid>div.col-lg-10{grid-column:span 10}.dynamic-form .grid>div.col-lg-11{grid-column:span 11}.dynamic-form .grid>div.col-lg-12{grid-column:span 12}}@media (min-width: 1200px){.dynamic-form .grid>div.col-xl-1{grid-column:span 1}.dynamic-form .grid>div.col-xl-2{grid-column:span 2}.dynamic-form .grid>div.col-xl-3{grid-column:span 3}.dynamic-form .grid>div.col-xl-4{grid-column:span 4}.dynamic-form .grid>div.col-xl-5{grid-column:span 5}.dynamic-form .grid>div.col-xl-6{grid-column:span 6}.dynamic-form .grid>div.col-xl-7{grid-column:span 7}.dynamic-form .grid>div.col-xl-8{grid-column:span 8}.dynamic-form .grid>div.col-xl-9{grid-column:span 9}.dynamic-form .grid>div.col-xl-10{grid-column:span 10}.dynamic-form .grid>div.col-xl-11{grid-column:span 11}.dynamic-form .grid>div.col-xl-12{grid-column:span 12}}.dynamic-form .sections-container{display:flex;flex-direction:column;gap:1rem;margin-top:1rem}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-header{background-color:var(--surface-50);border-color:var(--surface-200)}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-header .section-header{display:flex;align-items:center;gap:.5rem;width:100%}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-header .section-header .section-icon{font-size:1.1rem;color:var(--primary-color)}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-header .section-header .section-title{font-weight:600;color:var(--text-color)}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-content{padding:1.5rem}.dynamic-form .fixed-filters-section{margin-bottom:1rem;padding-bottom:1rem;border-bottom:1px solid var(--surface-200)}.dynamic-form .filter-sections-container{display:flex;flex-direction:column;gap:1rem;margin-top:1rem}.dynamic-form .form-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--surface-200)}.dialog-footer{display:flex;justify-content:flex-end;gap:.5rem}.drawer-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:1rem;border-top:1px solid var(--surface-200)}.inline-edit-fields{display:flex;flex-direction:column;gap:1rem}.inline-edit-fields .inline-edit-field{display:flex;flex-direction:column;gap:.25rem}.inline-edit-fields .inline-edit-field .field-label{font-weight:500;font-size:.875rem;color:var(--text-color-secondary);margin-bottom:.25rem}.inline-edit-fields .inline-edit-field .field-display{display:flex;align-items:center;justify-content:space-between;padding:.75rem;background-color:var(--surface-50);border:1px solid var(--surface-200);border-radius:var(--border-radius);cursor:pointer;transition:all .2s ease;min-height:42px}.inline-edit-fields .inline-edit-field .field-display:hover:not(.disabled){background-color:var(--surface-100);border-color:var(--primary-color)}.inline-edit-fields .inline-edit-field .field-display:hover:not(.disabled) .edit-icon{opacity:1}.inline-edit-fields .inline-edit-field .field-display.disabled{cursor:not-allowed;opacity:.6}.inline-edit-fields .inline-edit-field .field-display .display-value{flex:1;color:var(--text-color)}.inline-edit-fields .inline-edit-field .field-display .display-value:empty:before{content:attr(data-placeholder);color:var(--text-color-secondary);font-style:italic}.inline-edit-fields .inline-edit-field .field-display .edit-icon{color:var(--primary-color);font-size:.875rem;opacity:.5;transition:opacity .2s ease}.inline-edit-fields .inline-edit-field .field-edit{display:flex;flex-direction:column;gap:.5rem}.inline-edit-fields .inline-edit-field .field-edit .input-container{flex:1}.inline-edit-fields .inline-edit-field .field-edit .input-container ::ng-deep .field{gap:0}.inline-edit-fields .inline-edit-field .field-edit .input-container ::ng-deep .field label{display:none}.inline-edit-fields .inline-edit-field .field-edit .input-container ::ng-deep .field small.p-error{display:none}.inline-edit-fields .inline-edit-field .field-edit .edit-actions{display:flex;gap:.5rem;justify-content:flex-end}.inline-edit-fields .inline-edit-field .field-edit .field-error small.p-error{color:var(--p-red-500);font-size:.875rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PanelModule }, { kind: "component", type: i4$2.Panel, selector: "p-panel", inputs: ["toggleable", "header", "collapsed", "style", "styleClass", "iconPos", "expandIcon", "collapseIcon", "showHeader", "toggler", "transitionOptions", "toggleButtonProps"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "directive", type: i5.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i7$1.Drawer, selector: "p-drawer", inputs: ["appendTo", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i5$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }, { kind: "component", type: DynamicFieldTextComponent, selector: "sia-dynamic-field-text", inputs: ["field", "form", "mode"] }, { kind: "component", type: DynamicFieldNumberComponent, selector: "sia-dynamic-field-number", inputs: ["field", "form", "mode"] }, { kind: "component", type: DynamicFieldDateComponent, selector: "sia-dynamic-field-date", inputs: ["field", "form", "mode"] }, { kind: "component", type: DynamicFieldDropdownComponent, selector: "sia-dynamic-field-dropdown", inputs: ["field", "form", "mode"] }, { kind: "component", type: DynamicFieldLookupComponent, selector: "sia-dynamic-field-lookup", inputs: ["field", "form", "formGroup", "mode"] }, { kind: "component", type: DynamicFieldTextareaComponent, selector: "sia-dynamic-field-textarea", inputs: ["field", "form", "mode"] }] });
|
|
3827
|
+
}
|
|
3828
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DynamicFormComponent, decorators: [{
|
|
3829
|
+
type: Component,
|
|
3830
|
+
args: [{ selector: 'sia-dynamic-form', standalone: true, imports: [
|
|
3831
|
+
CommonModule,
|
|
3832
|
+
ReactiveFormsModule,
|
|
3833
|
+
PanelModule,
|
|
3834
|
+
DialogModule,
|
|
3835
|
+
DrawerModule,
|
|
3836
|
+
ButtonModule,
|
|
3837
|
+
TranslatePipe,
|
|
3838
|
+
DynamicFieldTextComponent,
|
|
3839
|
+
DynamicFieldNumberComponent,
|
|
3840
|
+
DynamicFieldDateComponent,
|
|
3841
|
+
DynamicFieldDropdownComponent,
|
|
3842
|
+
DynamicFieldLookupComponent,
|
|
3843
|
+
DynamicFieldTextareaComponent
|
|
3844
|
+
], template: "<!-- Modo Inline (padr\u00E3o) -->\n<ng-container *ngIf=\"isInlineMode\">\n <ng-container *ngTemplateOutlet=\"formContent\"></ng-container>\n</ng-container>\n\n<!-- Modo Dialog -->\n<p-dialog\n *ngIf=\"isDialogMode\"\n [(visible)]=\"visible\"\n [header]=\"dialogHeader | translate\"\n [modal]=\"true\"\n [style]=\"{ width: dialogWidth }\"\n [draggable]=\"dialogDraggable\"\n [resizable]=\"dialogResizable\"\n [maximizable]=\"dialogMaximizable\"\n [closeOnEscape]=\"dialogCloseOnEscape\"\n [dismissableMask]=\"dialogDismissableMask\"\n (onHide)=\"close()\">\n \n <ng-container *ngTemplateOutlet=\"formContent\"></ng-container>\n \n <ng-template pTemplate=\"footer\" *ngIf=\"showSubmitButton || showCancelButton\">\n <div class=\"dialog-footer\">\n <p-button\n *ngIf=\"showCancelButton\"\n [label]=\"cancelLabel | translate\"\n [icon]=\"cancelButtonIcon\"\n severity=\"secondary\"\n (onClick)=\"handleCancel()\"\n [disabled]=\"loading\">\n </p-button>\n <p-button\n *ngIf=\"showSubmitButton\"\n [label]=\"submitLabel | translate\"\n [icon]=\"submitButtonIcon\"\n (onClick)=\"handleSubmit()\"\n [loading]=\"loading\"\n [disabled]=\"form.invalid\">\n </p-button>\n </div>\n </ng-template>\n</p-dialog>\n\n<!-- Modo Drawer -->\n<p-drawer\n *ngIf=\"isDrawerMode\"\n [(visible)]=\"visible\"\n [header]=\"drawerHeader | translate\"\n [position]=\"drawerPosition\"\n [style]=\"drawerStyle\"\n [showCloseIcon]=\"drawerShowCloseIcon\"\n [closeOnEscape]=\"drawerCloseOnEscape\"\n [modal]=\"drawerDismissable\"\n (onHide)=\"close()\">\n \n <ng-container *ngTemplateOutlet=\"formContent\"></ng-container>\n \n <ng-template pTemplate=\"footer\" *ngIf=\"showSubmitButton || showCancelButton\">\n <div class=\"drawer-footer\">\n <p-button\n *ngIf=\"showCancelButton\"\n [label]=\"cancelLabel | translate\"\n [icon]=\"cancelButtonIcon\"\n severity=\"secondary\"\n (onClick)=\"handleCancel()\"\n [disabled]=\"loading\">\n </p-button>\n <p-button\n *ngIf=\"showSubmitButton\"\n [label]=\"submitLabel | translate\"\n [icon]=\"submitButtonIcon\"\n (onClick)=\"handleSubmit()\"\n [loading]=\"loading\"\n [disabled]=\"form.invalid\">\n </p-button>\n </div>\n </ng-template>\n</p-drawer>\n\n<!-- Template do Formul\u00E1rio (reutiliz\u00E1vel) -->\n<ng-template #formContent>\n <form [formGroup]=\"form\" class=\"dynamic-form\">\n \n <!-- Filtros Fixos (apenas no modo filter) -->\n <div *ngIf=\"mode === 'filter' && hasFixedFilters\" class=\"fixed-filters-section\">\n <div class=\"grid\">\n <ng-container *ngFor=\"let field of fixedFilters\">\n <div *ngIf=\"isFieldVisible(field)\" [ngClass]=\"getFieldGridClass(field)\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n </div>\n\n <!-- Campos Diretos (sem se\u00E7\u00E3o) -->\n <div *ngIf=\"hasFields\">\n <!-- Modo Inline Edit -->\n <div *ngIf=\"isInlineEditMode\" class=\"inline-edit-fields\">\n <ng-container *ngFor=\"let field of formFields\">\n <div *ngIf=\"isFieldVisible(field)\" class=\"inline-edit-field\">\n <ng-container *ngTemplateOutlet=\"inlineEditFieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n\n <!-- Modo Form Normal -->\n <div *ngIf=\"!isInlineEditMode\" class=\"grid\">\n <ng-container *ngFor=\"let field of formFields\">\n <div *ngIf=\"isFieldVisible(field)\" [ngClass]=\"getFieldGridClass(field)\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n </div>\n\n <!-- Se\u00E7\u00F5es de Campos (modo form) -->\n <div *ngIf=\"hasSections\" class=\"sections-container\">\n <p-panel \n *ngFor=\"let section of formSections\"\n [toggleable]=\"section.toggleable !== false\"\n [collapsed]=\"section.collapsed || false\"\n [styleClass]=\"'section-panel'\">\n \n <ng-template pTemplate=\"header\">\n <div class=\"section-header\">\n <i *ngIf=\"section.icon\" [class]=\"section.icon + ' section-icon'\"></i>\n <span class=\"section-title\">{{ section.title }}</span>\n </div>\n </ng-template>\n\n <!-- Modo Inline Edit -->\n <div *ngIf=\"isInlineEditMode\" class=\"inline-edit-fields\">\n <ng-container *ngFor=\"let field of section.fields\">\n <div *ngIf=\"isFieldVisible(field)\" class=\"inline-edit-field\">\n <ng-container *ngTemplateOutlet=\"inlineEditFieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n\n <!-- Modo Form Normal -->\n <div *ngIf=\"!isInlineEditMode\" class=\"grid\">\n <ng-container *ngFor=\"let field of section.fields\">\n <div *ngIf=\"isFieldVisible(field)\" [ngClass]=\"getFieldGridClass(field)\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n </p-panel>\n </div>\n\n <!-- Se\u00E7\u00F5es de Filtros (apenas no modo filter) -->\n <div *ngIf=\"mode === 'filter' && hasFilterSections\" class=\"filter-sections-container\">\n <p-panel \n *ngFor=\"let section of filterSections\"\n [header]=\"section.title\"\n [toggleable]=\"true\"\n [collapsed]=\"true\">\n\n <div class=\"grid\">\n <ng-container *ngFor=\"let field of section.fields\">\n <div *ngIf=\"isFieldVisible(field)\" [ngClass]=\"getFieldGridClass(field)\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n </ng-container>\n </div>\n </p-panel>\n </div>\n\n <!-- Bot\u00F5es inline (apenas para modo inline) -->\n <div *ngIf=\"isInlineMode && (showSubmitButton || showCancelButton)\" class=\"form-actions\">\n <p-button\n *ngIf=\"showCancelButton\"\n [label]=\"cancelLabel | translate\"\n [icon]=\"cancelButtonIcon\"\n severity=\"secondary\"\n (onClick)=\"handleCancel()\"\n [disabled]=\"loading\">\n </p-button>\n <p-button\n *ngIf=\"showSubmitButton\"\n [label]=\"submitLabel | translate\"\n [icon]=\"submitButtonIcon\"\n (onClick)=\"handleSubmit()\"\n [loading]=\"loading\"\n [disabled]=\"form.invalid\">\n </p-button>\n </div>\n\n </form>\n</ng-template>\n\n<!-- Template de Campo (reutiliz\u00E1vel) -->\n<ng-template #fieldTemplate let-field=\"field\">\n <!-- Text Field -->\n <sia-dynamic-field-text\n *ngIf=\"field.type === 'text'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-text>\n\n <!-- Textarea Field -->\n <sia-dynamic-field-textarea\n *ngIf=\"field.type === 'textarea'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-textarea>\n\n <!-- Number Field -->\n <sia-dynamic-field-number\n *ngIf=\"field.type === 'number'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-number>\n\n <!-- Date Field -->\n <sia-dynamic-field-date\n *ngIf=\"field.type === 'date'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-date>\n\n <!-- Dropdown/Enum Field -->\n <sia-dynamic-field-dropdown\n *ngIf=\"field.type === 'dropdown' || field.type === 'enum'\"\n [field]=\"field\"\n [form]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-dropdown>\n\n <!-- Lookup Field -->\n <sia-dynamic-field-lookup\n *ngIf=\"field.type === 'lookup'\"\n [field]=\"field\"\n [form]=\"form\"\n [formGroup]=\"form\"\n [mode]=\"fieldMode\">\n </sia-dynamic-field-lookup>\n</ng-template>\n\n\n<!-- Template de Campo Inline Edit -->\n<ng-template #inlineEditFieldTemplate let-field=\"field\">\n <div class=\"edit-inline-field\">\n <label class=\"field-label\">{{ field.label | translate }}</label>\n \n <!-- Display Mode -->\n <div class=\"field-display\" \n *ngIf=\"!isFieldEditing(field.field)\" \n (click)=\"startEditField(field.field)\"\n [class.disabled]=\"field.disabled\">\n <span class=\"display-value\" \n [attr.data-placeholder]=\"field.placeholder || ('crmx.business.not_informed' | translate)\">\n {{ getFieldDisplayValue(field.field) }}\n </span>\n <i class=\"pi pi-pencil edit-icon\" *ngIf=\"!field.disabled\"></i>\n </div>\n \n <!-- Edit Mode -->\n <div class=\"field-edit\" *ngIf=\"isFieldEditing(field.field)\">\n <div class=\"input-container\">\n <ng-container *ngTemplateOutlet=\"fieldTemplate; context: { field: field }\"></ng-container>\n </div>\n \n <!-- Action Buttons -->\n <div class=\"edit-actions\">\n <p-button \n type=\"button\" \n icon=\"pi pi-check\" \n [rounded]=\"true\"\n severity=\"primary\"\n (onClick)=\"saveInlineField(field.field)\" \n [loading]=\"savingField\"\n [disabled]=\"form.get(field.field)?.invalid\">\n </p-button>\n <p-button \n type=\"button\" \n icon=\"pi pi-times\" \n [rounded]=\"true\"\n severity=\"secondary\"\n (onClick)=\"cancelInlineEdit()\"\n [disabled]=\"savingField\">\n </p-button>\n </div>\n \n <!-- Error Message -->\n <div class=\"field-error\" *ngIf=\"form.get(field.field)?.invalid && form.get(field.field)?.touched\">\n <small class=\"p-error\">\n {{ form.get(field.field)?.errors?.['required'] ? ('crmx.business.required_field' | translate) : '' }}\n </small>\n </div>\n </div>\n </div>\n</ng-template>\n", styles: [".dynamic-form{width:100%}.dynamic-form .grid{display:grid;grid-template-columns:repeat(12,1fr);gap:1rem}.dynamic-form .grid>div.col-1{grid-column:span 1}.dynamic-form .grid>div.col-2{grid-column:span 2}.dynamic-form .grid>div.col-3{grid-column:span 3}.dynamic-form .grid>div.col-4{grid-column:span 4}.dynamic-form .grid>div.col-5{grid-column:span 5}.dynamic-form .grid>div.col-6{grid-column:span 6}.dynamic-form .grid>div.col-7{grid-column:span 7}.dynamic-form .grid>div.col-8{grid-column:span 8}.dynamic-form .grid>div.col-9{grid-column:span 9}.dynamic-form .grid>div.col-10{grid-column:span 10}.dynamic-form .grid>div.col-11{grid-column:span 11}.dynamic-form .grid>div.col-12{grid-column:span 12}@media (max-width: 575.98px){.dynamic-form .grid>div.col-xs-1{grid-column:span 1}.dynamic-form .grid>div.col-xs-2{grid-column:span 2}.dynamic-form .grid>div.col-xs-3{grid-column:span 3}.dynamic-form .grid>div.col-xs-4{grid-column:span 4}.dynamic-form .grid>div.col-xs-5{grid-column:span 5}.dynamic-form .grid>div.col-xs-6{grid-column:span 6}.dynamic-form .grid>div.col-xs-7{grid-column:span 7}.dynamic-form .grid>div.col-xs-8{grid-column:span 8}.dynamic-form .grid>div.col-xs-9{grid-column:span 9}.dynamic-form .grid>div.col-xs-10{grid-column:span 10}.dynamic-form .grid>div.col-xs-11{grid-column:span 11}.dynamic-form .grid>div.col-xs-12{grid-column:span 12}}@media (min-width: 576px){.dynamic-form .grid>div.col-sm-1{grid-column:span 1}.dynamic-form .grid>div.col-sm-2{grid-column:span 2}.dynamic-form .grid>div.col-sm-3{grid-column:span 3}.dynamic-form .grid>div.col-sm-4{grid-column:span 4}.dynamic-form .grid>div.col-sm-5{grid-column:span 5}.dynamic-form .grid>div.col-sm-6{grid-column:span 6}.dynamic-form .grid>div.col-sm-7{grid-column:span 7}.dynamic-form .grid>div.col-sm-8{grid-column:span 8}.dynamic-form .grid>div.col-sm-9{grid-column:span 9}.dynamic-form .grid>div.col-sm-10{grid-column:span 10}.dynamic-form .grid>div.col-sm-11{grid-column:span 11}.dynamic-form .grid>div.col-sm-12{grid-column:span 12}}@media (min-width: 768px){.dynamic-form .grid>div.col-md-1{grid-column:span 1}.dynamic-form .grid>div.col-md-2{grid-column:span 2}.dynamic-form .grid>div.col-md-3{grid-column:span 3}.dynamic-form .grid>div.col-md-4{grid-column:span 4}.dynamic-form .grid>div.col-md-5{grid-column:span 5}.dynamic-form .grid>div.col-md-6{grid-column:span 6}.dynamic-form .grid>div.col-md-7{grid-column:span 7}.dynamic-form .grid>div.col-md-8{grid-column:span 8}.dynamic-form .grid>div.col-md-9{grid-column:span 9}.dynamic-form .grid>div.col-md-10{grid-column:span 10}.dynamic-form .grid>div.col-md-11{grid-column:span 11}.dynamic-form .grid>div.col-md-12{grid-column:span 12}}@media (min-width: 992px){.dynamic-form .grid>div.col-lg-1{grid-column:span 1}.dynamic-form .grid>div.col-lg-2{grid-column:span 2}.dynamic-form .grid>div.col-lg-3{grid-column:span 3}.dynamic-form .grid>div.col-lg-4{grid-column:span 4}.dynamic-form .grid>div.col-lg-5{grid-column:span 5}.dynamic-form .grid>div.col-lg-6{grid-column:span 6}.dynamic-form .grid>div.col-lg-7{grid-column:span 7}.dynamic-form .grid>div.col-lg-8{grid-column:span 8}.dynamic-form .grid>div.col-lg-9{grid-column:span 9}.dynamic-form .grid>div.col-lg-10{grid-column:span 10}.dynamic-form .grid>div.col-lg-11{grid-column:span 11}.dynamic-form .grid>div.col-lg-12{grid-column:span 12}}@media (min-width: 1200px){.dynamic-form .grid>div.col-xl-1{grid-column:span 1}.dynamic-form .grid>div.col-xl-2{grid-column:span 2}.dynamic-form .grid>div.col-xl-3{grid-column:span 3}.dynamic-form .grid>div.col-xl-4{grid-column:span 4}.dynamic-form .grid>div.col-xl-5{grid-column:span 5}.dynamic-form .grid>div.col-xl-6{grid-column:span 6}.dynamic-form .grid>div.col-xl-7{grid-column:span 7}.dynamic-form .grid>div.col-xl-8{grid-column:span 8}.dynamic-form .grid>div.col-xl-9{grid-column:span 9}.dynamic-form .grid>div.col-xl-10{grid-column:span 10}.dynamic-form .grid>div.col-xl-11{grid-column:span 11}.dynamic-form .grid>div.col-xl-12{grid-column:span 12}}.dynamic-form .sections-container{display:flex;flex-direction:column;gap:1rem;margin-top:1rem}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-header{background-color:var(--surface-50);border-color:var(--surface-200)}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-header .section-header{display:flex;align-items:center;gap:.5rem;width:100%}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-header .section-header .section-icon{font-size:1.1rem;color:var(--primary-color)}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-header .section-header .section-title{font-weight:600;color:var(--text-color)}.dynamic-form .sections-container ::ng-deep .section-panel .p-panel-content{padding:1.5rem}.dynamic-form .fixed-filters-section{margin-bottom:1rem;padding-bottom:1rem;border-bottom:1px solid var(--surface-200)}.dynamic-form .filter-sections-container{display:flex;flex-direction:column;gap:1rem;margin-top:1rem}.dynamic-form .form-actions{display:flex;justify-content:flex-end;gap:.5rem;margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--surface-200)}.dialog-footer{display:flex;justify-content:flex-end;gap:.5rem}.drawer-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:1rem;border-top:1px solid var(--surface-200)}.inline-edit-fields{display:flex;flex-direction:column;gap:1rem}.inline-edit-fields .inline-edit-field{display:flex;flex-direction:column;gap:.25rem}.inline-edit-fields .inline-edit-field .field-label{font-weight:500;font-size:.875rem;color:var(--text-color-secondary);margin-bottom:.25rem}.inline-edit-fields .inline-edit-field .field-display{display:flex;align-items:center;justify-content:space-between;padding:.75rem;background-color:var(--surface-50);border:1px solid var(--surface-200);border-radius:var(--border-radius);cursor:pointer;transition:all .2s ease;min-height:42px}.inline-edit-fields .inline-edit-field .field-display:hover:not(.disabled){background-color:var(--surface-100);border-color:var(--primary-color)}.inline-edit-fields .inline-edit-field .field-display:hover:not(.disabled) .edit-icon{opacity:1}.inline-edit-fields .inline-edit-field .field-display.disabled{cursor:not-allowed;opacity:.6}.inline-edit-fields .inline-edit-field .field-display .display-value{flex:1;color:var(--text-color)}.inline-edit-fields .inline-edit-field .field-display .display-value:empty:before{content:attr(data-placeholder);color:var(--text-color-secondary);font-style:italic}.inline-edit-fields .inline-edit-field .field-display .edit-icon{color:var(--primary-color);font-size:.875rem;opacity:.5;transition:opacity .2s ease}.inline-edit-fields .inline-edit-field .field-edit{display:flex;flex-direction:column;gap:.5rem}.inline-edit-fields .inline-edit-field .field-edit .input-container{flex:1}.inline-edit-fields .inline-edit-field .field-edit .input-container ::ng-deep .field{gap:0}.inline-edit-fields .inline-edit-field .field-edit .input-container ::ng-deep .field label{display:none}.inline-edit-fields .inline-edit-field .field-edit .input-container ::ng-deep .field small.p-error{display:none}.inline-edit-fields .inline-edit-field .field-edit .edit-actions{display:flex;gap:.5rem;justify-content:flex-end}.inline-edit-fields .inline-edit-field .field-edit .field-error small.p-error{color:var(--p-red-500);font-size:.875rem}\n"] }]
|
|
3845
|
+
}], ctorParameters: () => [{ type: i2$1.FormBuilder }, { type: TranslationService }], propDecorators: { formFields: [{
|
|
3846
|
+
type: Input
|
|
3847
|
+
}], formSections: [{
|
|
3848
|
+
type: Input
|
|
3849
|
+
}], fixedFilters: [{
|
|
3850
|
+
type: Input
|
|
3851
|
+
}], filterSections: [{
|
|
3852
|
+
type: Input
|
|
3853
|
+
}], entityData: [{
|
|
3854
|
+
type: Input
|
|
3855
|
+
}], mode: [{
|
|
3856
|
+
type: Input
|
|
3857
|
+
}], displayMode: [{
|
|
3858
|
+
type: Input
|
|
3859
|
+
}], visible: [{
|
|
3860
|
+
type: Input
|
|
3861
|
+
}], dialogConfig: [{
|
|
3862
|
+
type: Input
|
|
3863
|
+
}], drawerConfig: [{
|
|
3864
|
+
type: Input
|
|
3865
|
+
}], showSubmitButton: [{
|
|
3866
|
+
type: Input
|
|
3867
|
+
}], showCancelButton: [{
|
|
3868
|
+
type: Input
|
|
3869
|
+
}], submitButtonLabel: [{
|
|
3870
|
+
type: Input
|
|
3871
|
+
}], cancelButtonLabel: [{
|
|
3872
|
+
type: Input
|
|
3873
|
+
}], submitButtonIcon: [{
|
|
3874
|
+
type: Input
|
|
3875
|
+
}], cancelButtonIcon: [{
|
|
3876
|
+
type: Input
|
|
3877
|
+
}], formReady: [{
|
|
3878
|
+
type: Output
|
|
3879
|
+
}], formSubmit: [{
|
|
3880
|
+
type: Output
|
|
3881
|
+
}], visibleChange: [{
|
|
3882
|
+
type: Output
|
|
3883
|
+
}], onCancel: [{
|
|
3884
|
+
type: Output
|
|
3885
|
+
}], fieldSave: [{
|
|
3886
|
+
type: Output
|
|
3887
|
+
}] } });
|
|
3888
|
+
|
|
3889
|
+
const apiInterceptor = (req, next) => {
|
|
3890
|
+
const authService = inject(AuthService);
|
|
3891
|
+
const router = inject(Router);
|
|
3892
|
+
// Get the access token, token type, and base URL
|
|
3893
|
+
const accessToken = authService.getAccessToken();
|
|
3894
|
+
const tokenType = authService.getTokenType();
|
|
3895
|
+
const baseUrl = authService.getApiBaseUrl();
|
|
3896
|
+
let modifiedReq = req;
|
|
3897
|
+
// Add Authorization header if token exists
|
|
3898
|
+
if (accessToken && tokenType) {
|
|
3899
|
+
const authHeader = `${tokenType} ${accessToken}`;
|
|
3900
|
+
modifiedReq = modifiedReq.clone({
|
|
3901
|
+
setHeaders: {
|
|
3902
|
+
Authorization: authHeader
|
|
3903
|
+
}
|
|
3904
|
+
});
|
|
3905
|
+
}
|
|
3906
|
+
// Prepend base URL if it exists and the request URL is relative
|
|
3907
|
+
if (baseUrl && !isAbsoluteUrl(modifiedReq.url)) {
|
|
3908
|
+
const fullUrl = combineUrls(baseUrl, modifiedReq.url);
|
|
3909
|
+
modifiedReq = modifiedReq.clone({
|
|
3910
|
+
url: fullUrl
|
|
3911
|
+
});
|
|
3912
|
+
}
|
|
3913
|
+
return next(modifiedReq).pipe(catchError$1((error) => {
|
|
3914
|
+
// Handle 401 Unauthorized errors
|
|
3915
|
+
if (error.status === 401) {
|
|
3916
|
+
// Redirect to forbidden route
|
|
3917
|
+
router.navigate(['/forbidden']);
|
|
3918
|
+
}
|
|
3919
|
+
// Re-throw the error so other error handlers can process it
|
|
3920
|
+
return throwError(() => error);
|
|
3921
|
+
}));
|
|
3922
|
+
};
|
|
3923
|
+
/**
|
|
3924
|
+
* Check if URL is absolute
|
|
3925
|
+
*/
|
|
3926
|
+
function isAbsoluteUrl(url) {
|
|
3927
|
+
return /^https?:\/\//.test(url);
|
|
3928
|
+
}
|
|
3929
|
+
/**
|
|
3930
|
+
* Combine base URL with relative URL
|
|
3931
|
+
*/
|
|
3932
|
+
function combineUrls(baseUrl, relativeUrl) {
|
|
3933
|
+
// Remove trailing slash from base URL
|
|
3934
|
+
const cleanBaseUrl = baseUrl.replace(/\/$/, '');
|
|
3935
|
+
// Remove leading slash from relative URL
|
|
3936
|
+
const cleanRelativeUrl = relativeUrl.replace(/^\//, '');
|
|
3937
|
+
return `${cleanBaseUrl}/${cleanRelativeUrl}`;
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
/*
|
|
3941
|
+
* Public API Surface of @seniorsistemas/components-ia
|
|
3942
|
+
*
|
|
3943
|
+
* Este arquivo será populado conforme os componentes forem migrados.
|
|
3944
|
+
* Por enquanto, exporta apenas o módulo principal.
|
|
3945
|
+
*/
|
|
3946
|
+
// ============================================================================
|
|
3947
|
+
// MODULE
|
|
3948
|
+
// ============================================================================
|
|
3949
|
+
// ============================================================================
|
|
3950
|
+
// NOTA: Os exports abaixo serão descomentados conforme os arquivos forem migrados
|
|
3951
|
+
// ============================================================================
|
|
3952
|
+
// COMPONENTS - BASE
|
|
3953
|
+
// export * from './lib/components/base/entity-list-base/entity-list-base.component';
|
|
3954
|
+
// export * from './lib/components/base/entity-form-base/entity-form-base.component';
|
|
3955
|
+
// COMPONENTS - FORMS
|
|
3956
|
+
// export * from './lib/components/forms/entity-form-fields/entity-form-fields.component';
|
|
3957
|
+
// export * from './lib/components/forms/entity-form-dialog/entity-form-dialog.component';
|
|
3958
|
+
// export * from './lib/components/forms/entity-form-drawer/entity-form-drawer.component';
|
|
3959
|
+
// export * from './lib/components/forms/sub-resource-form-dialog/sub-resource-form-dialog.component';
|
|
3960
|
+
// COMPONENTS - LISTS
|
|
3961
|
+
// export * from './lib/components/lists/form-filter/form-filter.component';
|
|
3962
|
+
// export * from './lib/components/lists/export-dialog/export-dialog.component';
|
|
3963
|
+
// export * from './lib/components/lists/bulk-delete-dialog/bulk-delete-dialog.component';
|
|
3964
|
+
// COMPONENTS - UI
|
|
3965
|
+
// export * from './lib/components/ui/breadcrumb/breadcrumb.component';
|
|
3966
|
+
// export * from './lib/components/ui/edit-inline/edit-inline.component';
|
|
3967
|
+
// export * from './lib/components/ui/lookup-field/lookup-field.component';
|
|
3968
|
+
// export * from './lib/components/ui/kanban-board/kanban-board.component';
|
|
3969
|
+
// export * from './lib/components/ui/theme-toggle/theme-toggle.component';
|
|
3970
|
+
// SERVICES
|
|
3971
|
+
// export * from './lib/services/entity.service';
|
|
3972
|
+
// export * from './lib/services/auth.service';
|
|
3973
|
+
// export * from './lib/services/translation.service';
|
|
3974
|
+
// export * from './lib/services/theme.service';
|
|
3975
|
+
// export * from './lib/services/permission.service';
|
|
3976
|
+
// export * from './lib/services/cookie.service';
|
|
3977
|
+
// export * from './lib/services/senior-token.service';
|
|
3978
|
+
// export * from './lib/services/mask.service';
|
|
3979
|
+
// DIRECTIVES
|
|
3980
|
+
// export * from './lib/directives/cep-mask.directive';
|
|
3981
|
+
// export * from './lib/directives/document-mask.directive';
|
|
3982
|
+
// export * from './lib/directives/phone-mask.directive';
|
|
3983
|
+
// PIPES
|
|
3984
|
+
// export * from './lib/pipes/translate.pipe';
|
|
3985
|
+
// INTERCEPTORS
|
|
3986
|
+
// export * from './lib/interceptors/api.interceptor';
|
|
3987
|
+
// MODELS - INTERFACES
|
|
3988
|
+
// export * from './lib/models/interfaces/base-entity.interface';
|
|
3989
|
+
// export * from './lib/models/interfaces/entity-config.interface';
|
|
3990
|
+
// export * from './lib/models/interfaces/entity-list.config';
|
|
3991
|
+
// MODELS - ENUMS
|
|
3992
|
+
// export * from './lib/models/enums/status.enum';
|
|
3993
|
+
// export * from './lib/models/enums/type-person.enum';
|
|
3994
|
+
// UTILS
|
|
3995
|
+
// export * from './lib/utils/utils';
|
|
3996
|
+
// I18N
|
|
3997
|
+
// export * from './lib/i18n/translation.config';
|
|
3998
|
+
// export * from './lib/i18n/locale.config';
|
|
3999
|
+
// export * from './lib/i18n/fallback';
|
|
4000
|
+
|
|
4001
|
+
/**
|
|
4002
|
+
* Generated bundle index. Do not edit.
|
|
4003
|
+
*/
|
|
4004
|
+
|
|
4005
|
+
export { AngularComponentsModule, AuthService, BreadcrumbComponent, BulkDeleteDialogComponent, CookieService, DEFAULT_LANGUAGE, DynamicFieldDateComponent, DynamicFieldDropdownComponent, DynamicFieldLookupComponent, DynamicFieldNumberComponent, DynamicFieldTextComponent, DynamicFieldTextareaComponent, DynamicFieldWrapperComponent, DynamicFormComponent, EntityService, ExportDialogComponent, PermissionService, SUPPORTED_LANGUAGES, SeniorTokenService, TRANSLATION_CONFIG, ThemeService, TranslationService, apiInterceptor, getLanguageInfo, mapTokenLocaleToLanguage };
|
|
4006
|
+
//# sourceMappingURL=seniorsistemas-components-ai.mjs.map
|