valtech-components 2.0.1011 → 2.0.1013

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.
@@ -2,5 +2,5 @@
2
2
  * Current version of valtech-components.
3
3
  * This is automatically updated during the publish process.
4
4
  */
5
- export const VERSION = '2.0.1011';
6
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsVUFBVSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDdXJyZW50IHZlcnNpb24gb2YgdmFsdGVjaC1jb21wb25lbnRzLlxuICogVGhpcyBpcyBhdXRvbWF0aWNhbGx5IHVwZGF0ZWQgZHVyaW5nIHRoZSBwdWJsaXNoIHByb2Nlc3MuXG4gKi9cbmV4cG9ydCBjb25zdCBWRVJTSU9OID0gJzIuMC4xMDExJztcbiJdfQ==
5
+ export const VERSION = '2.0.1013';
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsVUFBVSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDdXJyZW50IHZlcnNpb24gb2YgdmFsdGVjaC1jb21wb25lbnRzLlxuICogVGhpcyBpcyBhdXRvbWF0aWNhbGx5IHVwZGF0ZWQgZHVyaW5nIHRoZSBwdWJsaXNoIHByb2Nlc3MuXG4gKi9cbmV4cG9ydCBjb25zdCBWRVJTSU9OID0gJzIuMC4xMDEzJztcbiJdfQ==
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
54
54
  * Current version of valtech-components.
55
55
  * This is automatically updated during the publish process.
56
56
  */
57
- const VERSION = '2.0.1011';
57
+ const VERSION = '2.0.1013';
58
58
 
59
59
  // Control de estado de refresco (singleton a nivel de módulo)
60
60
  let isRefreshing = false;
@@ -26026,7 +26026,7 @@ class LinksAccordionComponent {
26026
26026
  </ng-container>
26027
26027
  </ion-accordion-group>
26028
26028
  </div>
26029
- `, isInline: true, styles: [".links-accordion .accordion-label{font-size:1.3rem}.links-accordion .accordion-item-label{font-size:1.1rem}.links-accordion ion-item{--background: var(--ion-color-light-tint)}.links-accordion ion-accordion{background:transparent}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IonAccordionGroup, selector: "ion-accordion-group", inputs: ["animated", "disabled", "expand", "mode", "multiple", "readonly", "value"] }, { kind: "component", type: IonAccordion, selector: "ion-accordion", inputs: ["disabled", "mode", "readonly", "toggleIcon", "toggleIconSlot", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }] }); }
26029
+ `, isInline: true, styles: [".links-accordion .accordion-label{font-size:1.3rem}.links-accordion .accordion-item-label{font-size:1.1rem}.links-accordion ion-item{--background: var(--ion-color-light-tint)}.links-accordion ion-accordion{background:transparent}:host-context(body.dark) .links-accordion ion-item,:host-context(html.ion-palette-dark) .links-accordion ion-item{--background: rgba(255, 255, 255, .045)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1$5.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IonAccordionGroup, selector: "ion-accordion-group", inputs: ["animated", "disabled", "expand", "mode", "multiple", "readonly", "value"] }, { kind: "component", type: IonAccordion, selector: "ion-accordion", inputs: ["disabled", "mode", "readonly", "toggleIcon", "toggleIconSlot", "value"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }] }); }
26030
26030
  }
26031
26031
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: LinksAccordionComponent, decorators: [{
26032
26032
  type: Component,
@@ -26077,7 +26077,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
26077
26077
  </ng-container>
26078
26078
  </ion-accordion-group>
26079
26079
  </div>
26080
- `, styles: [".links-accordion .accordion-label{font-size:1.3rem}.links-accordion .accordion-item-label{font-size:1.1rem}.links-accordion ion-item{--background: var(--ion-color-light-tint)}.links-accordion ion-accordion{background:transparent}\n"] }]
26080
+ `, styles: [".links-accordion .accordion-label{font-size:1.3rem}.links-accordion .accordion-item-label{font-size:1.1rem}.links-accordion ion-item{--background: var(--ion-color-light-tint)}.links-accordion ion-accordion{background:transparent}:host-context(body.dark) .links-accordion ion-item,:host-context(html.ion-palette-dark) .links-accordion ion-item{--background: rgba(255, 255, 255, .045)}\n"] }]
26081
26081
  }], ctorParameters: () => [{ type: i1$4.Router }, { type: i2$1.MenuController }], propDecorators: { props: [{
26082
26082
  type: Input
26083
26083
  }], navigate: [{
@@ -43137,6 +43137,18 @@ const SETTINGS_SECTIONS_CATALOG = [
43137
43137
  showChevron: true,
43138
43138
  shadowed: true,
43139
43139
  },
43140
+ {
43141
+ // CORE: gestión de organización (miembros, roles, transferencia). Montada
43142
+ // por `provideValtechSettingsRoutes` (includeOrganization default true), así
43143
+ // que TODAS las apps del factory la heredan en el hub sin `include`.
43144
+ token: 'action-organization',
43145
+ titleKey: 'organization',
43146
+ descKey: 'organizationDesc',
43147
+ ionicon: 'people-outline',
43148
+ routerLink: '/app/settings/organization',
43149
+ showChevron: true,
43150
+ shadowed: true,
43151
+ },
43140
43152
  {
43141
43153
  token: 'action-profile',
43142
43154
  titleKey: 'editProfile',
@@ -43265,6 +43277,8 @@ const SETTINGS_HUB_LABELS_I18N = {
43265
43277
  generalDesc: 'Apariencia e idioma',
43266
43278
  account: 'Cuenta',
43267
43279
  accountDesc: 'Organizaciones y sesión',
43280
+ organization: 'Organización',
43281
+ organizationDesc: 'Miembros, roles y datos de tu organización',
43268
43282
  editProfile: 'Editar perfil',
43269
43283
  editProfileDesc: 'Actualiza tu información personal',
43270
43284
  preferences: 'Preferencias',
@@ -43282,6 +43296,8 @@ const SETTINGS_HUB_LABELS_I18N = {
43282
43296
  generalDesc: 'Appearance and language',
43283
43297
  account: 'Account',
43284
43298
  accountDesc: 'Organizations and session',
43299
+ organization: 'Organization',
43300
+ organizationDesc: 'Members, roles and your organization data',
43285
43301
  editProfile: 'Edit profile',
43286
43302
  editProfileDesc: 'Update your personal information',
43287
43303
  preferences: 'Preferences',
@@ -49388,9 +49404,76 @@ class ApiKeysModalComponent {
49388
49404
  this.errorMsg = signal('');
49389
49405
  this.secret = signal(null);
49390
49406
  this.copied = signal(false);
49391
- this.name = '';
49392
- this.expiresInDays = null;
49393
- this.selectedPerms = signal(new Set());
49407
+ /**
49408
+ * FormMetadata del form de crear key (val-form). Reactivo a idioma + permisos
49409
+ * disponibles + estado submitting. Reemplaza los ion-input/ion-checkbox crudos
49410
+ * por val-form (consistencia con el resto de modales del factory).
49411
+ */
49412
+ this.createForm = computed(() => {
49413
+ this.i18n.lang();
49414
+ const working = this.submitting();
49415
+ const nameField = {
49416
+ token: 'apikey-name',
49417
+ name: 'name',
49418
+ label: this.t('nameLabel'),
49419
+ hint: '',
49420
+ placeholder: this.t('namePlaceholder'),
49421
+ type: InputType.TEXT,
49422
+ order: 1,
49423
+ validators: [Validators.required, Validators.maxLength(120)],
49424
+ errors: { required: this.t('noName') },
49425
+ value: '',
49426
+ state: ComponentStates.ENABLED,
49427
+ };
49428
+ const permsField = {
49429
+ token: 'apikey-perms',
49430
+ name: 'permissions',
49431
+ label: this.t('permissionsLabel'),
49432
+ hint: '',
49433
+ placeholder: '',
49434
+ type: InputType.MULTI_SELECT,
49435
+ order: 2,
49436
+ validators: [Validators.required],
49437
+ errors: { required: this.t('noPerms') },
49438
+ value: '',
49439
+ options: this.availablePermissions().map((p, i) => ({
49440
+ id: p,
49441
+ name: p,
49442
+ order: i + 1,
49443
+ })),
49444
+ state: ComponentStates.ENABLED,
49445
+ };
49446
+ const expiresField = {
49447
+ token: 'apikey-expires',
49448
+ name: 'expiresInDays',
49449
+ label: this.t('expiresLabel'),
49450
+ hint: '',
49451
+ placeholder: '',
49452
+ type: InputType.NUMBER,
49453
+ order: 3,
49454
+ validators: [Validators.min(1)],
49455
+ errors: {},
49456
+ value: '',
49457
+ state: ComponentStates.ENABLED,
49458
+ };
49459
+ const submitButton = {
49460
+ token: 'apikey-create-submit',
49461
+ text: this.t('create'),
49462
+ color: 'dark',
49463
+ type: 'submit',
49464
+ fill: 'solid',
49465
+ size: 'default',
49466
+ shape: 'round',
49467
+ expand: 'block',
49468
+ state: working ? ComponentStates.WORKING : ComponentStates.ENABLED,
49469
+ };
49470
+ return {
49471
+ name: '',
49472
+ state: working ? ComponentStates.WORKING : ComponentStates.ENABLED,
49473
+ sections: [{ name: '', order: 1, fields: [nameField, permsField, expiresField] }],
49474
+ actions: submitButton,
49475
+ };
49476
+ });
49394
49477
  if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
49395
49478
  this.i18n.registerContent(DEFAULT_NAMESPACE$5, API_KEYS_MODAL_I18N);
49396
49479
  }
@@ -49417,39 +49500,36 @@ class ApiKeysModalComponent {
49417
49500
  }
49418
49501
  openCreate() {
49419
49502
  this.errorMsg.set('');
49420
- this.name = '';
49421
- this.expiresInDays = null;
49422
- this.selectedPerms.set(new Set());
49423
49503
  this.showCreate.set(true);
49424
49504
  }
49425
- togglePerm(perm, ev) {
49426
- const checked = ev.detail?.checked ?? false;
49427
- const next = new Set(this.selectedPerms());
49428
- if (checked) {
49429
- next.add(perm);
49430
- }
49431
- else {
49432
- next.delete(perm);
49433
- }
49434
- this.selectedPerms.set(next);
49435
- }
49436
- submit() {
49505
+ onCreateSubmit(event) {
49506
+ if (this.submitting())
49507
+ return;
49437
49508
  this.errorMsg.set('');
49438
- if (!this.name.trim()) {
49509
+ const name = String(event.fields['name'] ?? '').trim();
49510
+ // MULTI_SELECT puede devolver array o csv según el render — normalizamos.
49511
+ const rawPerms = event.fields['permissions'];
49512
+ const permissions = Array.isArray(rawPerms)
49513
+ ? rawPerms
49514
+ : String(rawPerms ?? '')
49515
+ .split(',')
49516
+ .map(s => s.trim())
49517
+ .filter(Boolean);
49518
+ const expiresRaw = Number(event.fields['expiresInDays']);
49519
+ if (!name) {
49439
49520
  this.errorMsg.set(this.t('noName'));
49440
49521
  return;
49441
49522
  }
49442
- const permissions = Array.from(this.selectedPerms());
49443
- if (permissions.length === 0) {
49523
+ if (!permissions.length) {
49444
49524
  this.errorMsg.set(this.t('noPerms'));
49445
49525
  return;
49446
49526
  }
49447
49527
  this.submitting.set(true);
49448
49528
  this.apiKeys
49449
49529
  .create({
49450
- name: this.name.trim(),
49530
+ name,
49451
49531
  permissions,
49452
- expiresInDays: this.expiresInDays && this.expiresInDays > 0 ? this.expiresInDays : undefined,
49532
+ expiresInDays: Number.isFinite(expiresRaw) && expiresRaw > 0 ? expiresRaw : undefined,
49453
49533
  })
49454
49534
  .subscribe({
49455
49535
  next: res => {
@@ -49554,45 +49634,13 @@ class ApiKeysModalComponent {
49554
49634
  <p class="apikey-error">{{ errorMsg() }}</p>
49555
49635
  }
49556
49636
 
49557
- <!-- Crear -->
49637
+ <!-- Crear — val-form (consistencia con el resto de modales del factory). -->
49558
49638
  @if (showCreate()) {
49559
49639
  <div class="apikey-create">
49560
- <ion-input
49561
- label="{{ t('nameLabel') }}"
49562
- labelPlacement="stacked"
49563
- [placeholder]="t('namePlaceholder')"
49564
- [(ngModel)]="name"
49565
- />
49566
- <p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
49567
- <div class="apikey-perms">
49568
- @for (p of availablePermissions(); track p) {
49569
- <ion-checkbox
49570
- labelPlacement="end"
49571
- [checked]="selectedPerms().has(p)"
49572
- (ionChange)="togglePerm(p, $event)"
49573
- >
49574
- {{ p }}
49575
- </ion-checkbox>
49576
- }
49577
- </div>
49578
- <ion-input
49579
- label="{{ t('expiresLabel') }}"
49580
- labelPlacement="stacked"
49581
- type="number"
49582
- [(ngModel)]="expiresInDays"
49583
- />
49584
- <div class="apikey-actions">
49585
- <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49586
- @if (submitting()) {
49587
- <ion-spinner name="dots" />&nbsp;{{ t('creating') }}
49588
- } @else {
49589
- {{ t('create') }}
49590
- }
49591
- </ion-button>
49592
- <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49593
- {{ t('cancel') }}
49594
- </ion-button>
49595
- </div>
49640
+ <val-form [props]="createForm()" (onSubmit)="onCreateSubmit($event)" />
49641
+ <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49642
+ {{ t('cancel') }}
49643
+ </ion-button>
49596
49644
  </div>
49597
49645
  } @else {
49598
49646
  <div class="apikey-actions">
@@ -49604,20 +49652,18 @@ class ApiKeysModalComponent {
49604
49652
  }
49605
49653
  }
49606
49654
  </ion-content>
49607
- `, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.apikey-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.apikey-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:10px;border:1.5px solid var(--ion-color-light)}.apikey-row__main{display:flex;flex-direction:column}.apikey-row__name{font-weight:600;color:var(--ion-color-dark)}.apikey-row__meta{font-size:12px;color:var(--ion-color-medium-shade, #6b6e76)}.apikey-create{margin-top:8px;padding:16px;border-radius:12px;background:var(--ion-color-light)}.apikey-create__label{margin:12px 0 4px;font-size:13px;font-weight:600;color:var(--ion-color-dark)}.apikey-perms{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;font-size:13px}.apikey-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}.apikey-error{margin:8px 0;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.apikey-secret__warn{font-size:.875rem;color:var(--ion-color-warning-shade, #b88600);margin:0 0 12px}.apikey-secret__box{padding:12px;border-radius:8px;background:var(--ion-color-dark);color:#fff;word-break:break-all;font-family:monospace;font-size:13px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$7.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$7.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonInput, selector: "ion-input", inputs: ["accept", "autocapitalize", "autocomplete", "autocorrect", "autofocus", "clearInput", "clearOnEdit", "color", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "max", "maxlength", "min", "minlength", "mode", "multiple", "name", "pattern", "placeholder", "readonly", "required", "shape", "size", "spellcheck", "step", "type", "value"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
49655
+ `, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:12px}.apikey-list{display:flex;flex-direction:column;gap:8px;margin-bottom:16px}.apikey-row{display:flex;align-items:center;justify-content:space-between;padding:12px;border-radius:10px;border:1.5px solid var(--ion-color-light)}.apikey-row__main{display:flex;flex-direction:column}.apikey-row__name{font-weight:600;color:var(--ion-color-dark)}.apikey-row__meta{font-size:12px;color:var(--ion-color-medium-shade, #6b6e76)}.apikey-create{margin-top:8px;padding:16px;border-radius:12px;background:var(--ion-color-light)}.apikey-create__label{margin:12px 0 4px;font-size:13px;font-weight:600;color:var(--ion-color-dark)}.apikey-perms{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;font-size:13px}.apikey-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}.apikey-error{margin:8px 0;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.apikey-secret__warn{font-size:.875rem;color:var(--ion-color-warning-shade, #b88600);margin:0 0 12px}.apikey-secret__box{padding:12px;border-radius:8px;background:var(--ion-color-dark);color:#fff;word-break:break-all;font-family:monospace;font-size:13px}\n"], dependencies: [{ kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }] }); }
49608
49656
  }
49609
49657
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ApiKeysModalComponent, decorators: [{
49610
49658
  type: Component,
49611
49659
  args: [{ selector: 'val-api-keys-modal', standalone: true, imports: [
49612
- FormsModule,
49613
49660
  IonHeader,
49614
49661
  IonToolbar,
49615
49662
  IonButtons,
49616
49663
  IonButton,
49617
49664
  IonContent,
49618
- IonInput,
49619
- IonCheckbox,
49620
49665
  IonSpinner,
49666
+ FormComponent,
49621
49667
  DisplayComponent,
49622
49668
  TextComponent,
49623
49669
  TitleComponent,
@@ -49683,45 +49729,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
49683
49729
  <p class="apikey-error">{{ errorMsg() }}</p>
49684
49730
  }
49685
49731
 
49686
- <!-- Crear -->
49732
+ <!-- Crear — val-form (consistencia con el resto de modales del factory). -->
49687
49733
  @if (showCreate()) {
49688
49734
  <div class="apikey-create">
49689
- <ion-input
49690
- label="{{ t('nameLabel') }}"
49691
- labelPlacement="stacked"
49692
- [placeholder]="t('namePlaceholder')"
49693
- [(ngModel)]="name"
49694
- />
49695
- <p class="apikey-create__label">{{ t('permissionsLabel') }}</p>
49696
- <div class="apikey-perms">
49697
- @for (p of availablePermissions(); track p) {
49698
- <ion-checkbox
49699
- labelPlacement="end"
49700
- [checked]="selectedPerms().has(p)"
49701
- (ionChange)="togglePerm(p, $event)"
49702
- >
49703
- {{ p }}
49704
- </ion-checkbox>
49705
- }
49706
- </div>
49707
- <ion-input
49708
- label="{{ t('expiresLabel') }}"
49709
- labelPlacement="stacked"
49710
- type="number"
49711
- [(ngModel)]="expiresInDays"
49712
- />
49713
- <div class="apikey-actions">
49714
- <ion-button expand="block" color="dark" [disabled]="submitting()" (click)="submit()">
49715
- @if (submitting()) {
49716
- <ion-spinner name="dots" />&nbsp;{{ t('creating') }}
49717
- } @else {
49718
- {{ t('create') }}
49719
- }
49720
- </ion-button>
49721
- <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49722
- {{ t('cancel') }}
49723
- </ion-button>
49724
- </div>
49735
+ <val-form [props]="createForm()" (onSubmit)="onCreateSubmit($event)" />
49736
+ <ion-button expand="block" fill="clear" color="medium" (click)="showCreate.set(false)">
49737
+ {{ t('cancel') }}
49738
+ </ion-button>
49725
49739
  </div>
49726
49740
  } @else {
49727
49741
  <div class="apikey-actions">