valtech-components 4.0.13 → 4.0.14
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/esm2022/lib/components/organisms/member-import-modal/member-import-modal.component.mjs +157 -72
- package/esm2022/lib/components/organisms/member-import-modal/member-import-modal.i18n.mjs +17 -9
- package/esm2022/lib/version.mjs +2 -2
- package/fesm2022/valtech-components.mjs +169 -77
- package/fesm2022/valtech-components.mjs.map +1 -1
- package/lib/components/organisms/member-import-modal/member-import-modal.component.d.ts +4 -2
- package/lib/version.d.ts +1 -1
- package/package.json +1 -1
package/esm2022/lib/version.mjs
CHANGED
|
@@ -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 = '4.0.
|
|
6
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
5
|
+
export const VERSION = '4.0.14';
|
|
6
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdmVyc2lvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLENBQUMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDdXJyZW50IHZlcnNpb24gb2YgdmFsdGVjaC1jb21wb25lbnRzLlxuICogVGhpcyBpcyBhdXRvbWF0aWNhbGx5IHVwZGF0ZWQgZHVyaW5nIHRoZSBwdWJsaXNoIHByb2Nlc3MuXG4gKi9cbmV4cG9ydCBjb25zdCBWRVJTSU9OID0gJzQuMC4xNCc7XG4iXX0=
|
|
@@ -56,7 +56,7 @@ import { BrowserMultiFormatReader } from '@zxing/browser';
|
|
|
56
56
|
* Current version of valtech-components.
|
|
57
57
|
* This is automatically updated during the publish process.
|
|
58
58
|
*/
|
|
59
|
-
const VERSION = '4.0.
|
|
59
|
+
const VERSION = '4.0.14';
|
|
60
60
|
|
|
61
61
|
// Control de estado de refresco (singleton a nivel de módulo)
|
|
62
62
|
let isRefreshing = false;
|
|
@@ -50078,14 +50078,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50078
50078
|
const MEMBER_IMPORT_MODAL_I18N = {
|
|
50079
50079
|
es: {
|
|
50080
50080
|
title: 'Importar miembros',
|
|
50081
|
-
|
|
50081
|
+
subtitle: 'Carga un archivo CSV o pega el contenido directamente. Cada fila debe tener email, nombre y rol. Los cambios se aplican al instante.',
|
|
50082
50082
|
csvPlaceholder: 'email,nombre,rol\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
|
|
50083
|
+
importMethodLabel: '¿Cómo deseas importar?',
|
|
50084
|
+
importMethodFile: 'Por archivo',
|
|
50085
|
+
importMethodFileHint: 'Sube un archivo .csv desde tu equipo',
|
|
50086
|
+
importMethodPaste: 'Pegar contenido',
|
|
50087
|
+
importMethodPasteHint: 'Escribe o pega el CSV directamente',
|
|
50083
50088
|
uploadFile: 'Subir archivo CSV',
|
|
50084
|
-
orPaste: 'o pega el CSV abajo',
|
|
50085
50089
|
fileLoaded: 'Archivo cargado: {name}',
|
|
50086
50090
|
fileReadError: 'No se pudo leer el archivo.',
|
|
50087
50091
|
rolesAvailable: 'Roles disponibles:',
|
|
50088
|
-
onConflictLabel: '
|
|
50092
|
+
onConflictLabel: '¿Qué hacer si el email ya existe?',
|
|
50089
50093
|
conflictAssign: 'Asignar el rol',
|
|
50090
50094
|
conflictSkip: 'Omitir',
|
|
50091
50095
|
conflictError: 'Marcar error',
|
|
@@ -50093,7 +50097,7 @@ const MEMBER_IMPORT_MODAL_I18N = {
|
|
|
50093
50097
|
submit: 'Importar',
|
|
50094
50098
|
submitting: 'Importando...',
|
|
50095
50099
|
parseError: 'No se pudo interpretar el CSV. Revisa el formato.',
|
|
50096
|
-
noRows: '
|
|
50100
|
+
noRows: 'Agrega al menos una fila válida (email y rol).',
|
|
50097
50101
|
summary: 'Resumen',
|
|
50098
50102
|
created: 'creados',
|
|
50099
50103
|
assigned: 'asignados',
|
|
@@ -50110,14 +50114,18 @@ const MEMBER_IMPORT_MODAL_I18N = {
|
|
|
50110
50114
|
},
|
|
50111
50115
|
en: {
|
|
50112
50116
|
title: 'Import members',
|
|
50113
|
-
|
|
50117
|
+
subtitle: 'Upload a CSV file or paste the content directly. Each row must include email, name and role. Changes are applied immediately.',
|
|
50114
50118
|
csvPlaceholder: 'email,name,role\nana@x.com,Ana,editor\nbeto@x.com,Beto,viewer',
|
|
50119
|
+
importMethodLabel: 'How do you want to import?',
|
|
50120
|
+
importMethodFile: 'From file',
|
|
50121
|
+
importMethodFileHint: 'Upload a .csv file from your device',
|
|
50122
|
+
importMethodPaste: 'Paste content',
|
|
50123
|
+
importMethodPasteHint: 'Type or paste the CSV directly',
|
|
50115
50124
|
uploadFile: 'Upload CSV file',
|
|
50116
|
-
orPaste: 'or paste the CSV below',
|
|
50117
50125
|
fileLoaded: 'File loaded: {name}',
|
|
50118
50126
|
fileReadError: "Couldn't read the file.",
|
|
50119
50127
|
rolesAvailable: 'Available roles:',
|
|
50120
|
-
onConflictLabel: '
|
|
50128
|
+
onConflictLabel: 'What to do if the email already exists?',
|
|
50121
50129
|
conflictAssign: 'Assign the role',
|
|
50122
50130
|
conflictSkip: 'Skip',
|
|
50123
50131
|
conflictError: 'Mark as error',
|
|
@@ -50125,7 +50133,7 @@ const MEMBER_IMPORT_MODAL_I18N = {
|
|
|
50125
50133
|
submit: 'Import',
|
|
50126
50134
|
submitting: 'Importing...',
|
|
50127
50135
|
parseError: "Couldn't parse the CSV. Check the format.",
|
|
50128
|
-
noRows: '
|
|
50136
|
+
noRows: 'Add at least one valid row (email and role).',
|
|
50129
50137
|
summary: 'Summary',
|
|
50130
50138
|
created: 'created',
|
|
50131
50139
|
assigned: 'assigned',
|
|
@@ -50143,7 +50151,7 @@ const MEMBER_IMPORT_MODAL_I18N = {
|
|
|
50143
50151
|
};
|
|
50144
50152
|
|
|
50145
50153
|
const DEFAULT_NAMESPACE$8 = 'Settings.ImportModal';
|
|
50146
|
-
addIcons({ cloudUploadOutline });
|
|
50154
|
+
addIcons({ cloudUploadOutline, documentTextOutline });
|
|
50147
50155
|
/**
|
|
50148
50156
|
* `val-member-import-modal` — panel de carga masiva de miembros (ADR-023 fase 6).
|
|
50149
50157
|
*
|
|
@@ -50164,13 +50172,13 @@ class MemberImportModalComponent {
|
|
|
50164
50172
|
/** Namespace i18n. */
|
|
50165
50173
|
this.i18nNamespace = DEFAULT_NAMESPACE$8;
|
|
50166
50174
|
this.csv = '';
|
|
50167
|
-
this.
|
|
50175
|
+
this.importMode = signal('file');
|
|
50168
50176
|
this.onConflictControl = new FormControl('assignRole');
|
|
50169
|
-
this.
|
|
50177
|
+
this.sendActivationControl = new FormControl(true);
|
|
50178
|
+
this.conflictRadioProps = computed(() => ({
|
|
50170
50179
|
control: this.onConflictControl,
|
|
50171
50180
|
label: '',
|
|
50172
|
-
|
|
50173
|
-
type: InputType.SELECT,
|
|
50181
|
+
type: InputType.RADIO,
|
|
50174
50182
|
state: ComponentStates.ENABLED,
|
|
50175
50183
|
options: [
|
|
50176
50184
|
{ id: 'assignRole', name: this.t('conflictAssign'), order: 0 },
|
|
@@ -50178,6 +50186,13 @@ class MemberImportModalComponent {
|
|
|
50178
50186
|
{ id: 'error', name: this.t('conflictError'), order: 2 },
|
|
50179
50187
|
],
|
|
50180
50188
|
}));
|
|
50189
|
+
this.sendActivationProps = computed(() => ({
|
|
50190
|
+
control: this.sendActivationControl,
|
|
50191
|
+
label: this.t('sendActivation'),
|
|
50192
|
+
type: InputType.CHECK,
|
|
50193
|
+
state: ComponentStates.ENABLED,
|
|
50194
|
+
labelPlacement: 'end',
|
|
50195
|
+
}));
|
|
50181
50196
|
this.submitting = signal(false);
|
|
50182
50197
|
this.errorMsg = signal('');
|
|
50183
50198
|
this.results = signal(null);
|
|
@@ -50248,7 +50263,7 @@ class MemberImportModalComponent {
|
|
|
50248
50263
|
.importMembers(this.orgId, {
|
|
50249
50264
|
users,
|
|
50250
50265
|
onConflict: (this.onConflictControl.value ?? 'assignRole'),
|
|
50251
|
-
sendActivation: this.
|
|
50266
|
+
sendActivation: this.sendActivationControl.value ?? true,
|
|
50252
50267
|
})
|
|
50253
50268
|
.subscribe({
|
|
50254
50269
|
next: res => {
|
|
@@ -50277,6 +50292,7 @@ class MemberImportModalComponent {
|
|
|
50277
50292
|
this.fileName.set('');
|
|
50278
50293
|
this.errorMsg.set('');
|
|
50279
50294
|
this.results.set(null);
|
|
50295
|
+
this.importMode.set('file');
|
|
50280
50296
|
}
|
|
50281
50297
|
dismiss() {
|
|
50282
50298
|
this._modalRef?.dismiss(null, 'cancel');
|
|
@@ -50298,10 +50314,9 @@ class MemberImportModalComponent {
|
|
|
50298
50314
|
|
|
50299
50315
|
<ion-content class="ion-padding">
|
|
50300
50316
|
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
50317
|
+
<val-title [props]="{ content: t('subtitle'), size: 'large', color: '', bold: false }" />
|
|
50301
50318
|
|
|
50302
50319
|
@if (!results()) {
|
|
50303
|
-
<val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
|
|
50304
|
-
|
|
50305
50320
|
@if (rolesHint()) {
|
|
50306
50321
|
<val-text
|
|
50307
50322
|
class="import-roles-hint"
|
|
@@ -50309,40 +50324,79 @@ class MemberImportModalComponent {
|
|
|
50309
50324
|
/>
|
|
50310
50325
|
}
|
|
50311
50326
|
|
|
50312
|
-
<!--
|
|
50313
|
-
|
|
50314
|
-
|
|
50315
|
-
<
|
|
50316
|
-
<
|
|
50317
|
-
|
|
50318
|
-
|
|
50319
|
-
|
|
50320
|
-
|
|
50327
|
+
<!-- Selector de método de importación -->
|
|
50328
|
+
<div class="import-section">
|
|
50329
|
+
<val-title [props]="{ content: t('importMethodLabel'), size: 'small', color: 'dark', bold: true }" />
|
|
50330
|
+
<div class="import-method-cards">
|
|
50331
|
+
<button
|
|
50332
|
+
type="button"
|
|
50333
|
+
class="import-method-card"
|
|
50334
|
+
[class.import-method-card--active]="importMode() === 'file'"
|
|
50335
|
+
(click)="importMode.set('file')"
|
|
50336
|
+
>
|
|
50337
|
+
<ion-icon name="cloud-upload-outline" class="import-method-card__icon" />
|
|
50338
|
+
<span class="import-method-card__title">{{ t('importMethodFile') }}</span>
|
|
50339
|
+
<span class="import-method-card__hint">{{ t('importMethodFileHint') }}</span>
|
|
50340
|
+
</button>
|
|
50341
|
+
<button
|
|
50342
|
+
type="button"
|
|
50343
|
+
class="import-method-card"
|
|
50344
|
+
[class.import-method-card--active]="importMode() === 'paste'"
|
|
50345
|
+
(click)="importMode.set('paste')"
|
|
50346
|
+
>
|
|
50347
|
+
<ion-icon name="document-text-outline" class="import-method-card__icon" />
|
|
50348
|
+
<span class="import-method-card__title">{{ t('importMethodPaste') }}</span>
|
|
50349
|
+
<span class="import-method-card__hint">{{ t('importMethodPasteHint') }}</span>
|
|
50350
|
+
</button>
|
|
50351
|
+
</div>
|
|
50321
50352
|
</div>
|
|
50322
50353
|
|
|
50323
|
-
|
|
50324
|
-
|
|
50325
|
-
|
|
50326
|
-
|
|
50354
|
+
<!-- Subida de archivo (label trick — gesto confiable en iOS/PWA) -->
|
|
50355
|
+
@if (importMode() === 'file') {
|
|
50356
|
+
<div class="import-section">
|
|
50357
|
+
<label class="import-upload__btn">
|
|
50358
|
+
<ion-icon name="cloud-upload-outline" />
|
|
50359
|
+
<span>{{ t('uploadFile') }}</span>
|
|
50360
|
+
<input type="file" accept=".csv,text/csv,text/plain" (change)="onFileSelected($event)" />
|
|
50361
|
+
</label>
|
|
50362
|
+
@if (fileName()) {
|
|
50363
|
+
<p class="import-file-loaded">
|
|
50364
|
+
{{ t('fileLoaded').replace('{name}', fileName()) }}
|
|
50365
|
+
</p>
|
|
50366
|
+
}
|
|
50367
|
+
</div>
|
|
50327
50368
|
}
|
|
50328
50369
|
|
|
50329
|
-
|
|
50330
|
-
|
|
50331
|
-
|
|
50332
|
-
|
|
50333
|
-
|
|
50334
|
-
|
|
50335
|
-
|
|
50370
|
+
<!-- Pegar CSV -->
|
|
50371
|
+
@if (importMode() === 'paste') {
|
|
50372
|
+
<div class="import-section">
|
|
50373
|
+
<ion-textarea
|
|
50374
|
+
class="import-csv"
|
|
50375
|
+
[autoGrow]="true"
|
|
50376
|
+
[rows]="6"
|
|
50377
|
+
[placeholder]="t('csvPlaceholder')"
|
|
50378
|
+
[(ngModel)]="csv"
|
|
50379
|
+
/>
|
|
50380
|
+
</div>
|
|
50381
|
+
}
|
|
50382
|
+
|
|
50383
|
+
<!-- Si hubo carga de archivo, mostrar textarea de solo lectura para revisar -->
|
|
50384
|
+
@if (importMode() === 'file' && csv) {
|
|
50385
|
+
<div class="import-section">
|
|
50386
|
+
<ion-textarea class="import-csv" [autoGrow]="true" [rows]="4" [readonly]="true" [value]="csv" />
|
|
50387
|
+
</div>
|
|
50388
|
+
}
|
|
50336
50389
|
|
|
50337
|
-
<!--
|
|
50338
|
-
<div class="
|
|
50339
|
-
<val-title [props]="{ content: t('onConflictLabel'), size: 'small', color: 'dark', bold:
|
|
50340
|
-
<val-
|
|
50390
|
+
<!-- Conflicto: radio buttons -->
|
|
50391
|
+
<div class="import-section">
|
|
50392
|
+
<val-title [props]="{ content: t('onConflictLabel'), size: 'small', color: 'dark', bold: true }" />
|
|
50393
|
+
<val-radio-input [props]="conflictRadioProps()" />
|
|
50341
50394
|
</div>
|
|
50342
50395
|
|
|
50343
|
-
|
|
50344
|
-
|
|
50345
|
-
|
|
50396
|
+
<!-- Activación: val-check-input -->
|
|
50397
|
+
<div class="import-section">
|
|
50398
|
+
<val-check-input [props]="sendActivationProps()" />
|
|
50399
|
+
</div>
|
|
50346
50400
|
|
|
50347
50401
|
@if (errorMsg()) {
|
|
50348
50402
|
<p class="import-error">{{ errorMsg() }}</p>
|
|
@@ -50379,7 +50433,7 @@ class MemberImportModalComponent {
|
|
|
50379
50433
|
</div>
|
|
50380
50434
|
}
|
|
50381
50435
|
</ion-content>
|
|
50382
|
-
`, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:
|
|
50436
|
+
`, isInline: true, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:8px}.import-roles-hint{display:block;margin-bottom:24px}.import-section{margin-bottom:28px}.import-section val-title{margin-bottom:10px}.import-method-cards{display:grid;grid-template-columns:1fr 1fr;gap:12px}.import-method-card{display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:14px 16px;border:1.5px solid var(--ion-color-light-shade, #d7d8da);border-radius:12px;background:var(--ion-color-light);cursor:pointer;text-align:left;transition:border-color .15s ease,background .15s ease}.import-method-card:hover{border-color:var(--ion-color-medium, #92949c)}.import-method-card--active{border-color:var(--ion-color-primary, #7026df);background:color-mix(in srgb,var(--ion-color-primary, #7026df) 6%,var(--ion-color-light))}.import-method-card__icon{font-size:1.375rem;color:var(--ion-color-medium-shade, #6b6e76);margin-bottom:4px}.import-method-card--active .import-method-card__icon{color:var(--ion-color-primary, #7026df)}.import-method-card__title{font-size:.875rem;font-weight:600;color:var(--ion-color-dark)}.import-method-card__hint{font-size:.75rem;color:var(--ion-color-medium, #92949c);line-height:1.3}.import-upload__btn{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;border:1px solid var(--ion-color-medium, #92949c);border-radius:10px;background:var(--ion-color-light);color:var(--ion-color-dark);font-size:.875rem;font-weight:600;cursor:pointer;transition:border-color .15s ease}.import-upload__btn:hover{border-color:var(--ion-color-primary, #7026df)}.import-upload__btn ion-icon{font-size:1.25rem}.import-upload__btn input[type=file]{display:none}.import-file-loaded{margin:10px 0 0;font-size:.8125rem;color:var(--ion-color-medium-shade, #6b6e76)}.import-csv{--background: var(--ion-color-light);border-radius:10px;font-family:monospace;font-size:.8125rem}.import-error{margin:0 0 12px;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.import-actions{margin-top:8px}.import-results{display:flex;flex-direction:column;gap:6px;margin:12px 0}.import-row{display:grid;grid-template-columns:1fr auto;grid-row-gap:2px;align-items:center;padding:10px 12px;border-radius:8px;border-left:3px solid var(--ion-color-medium);background:var(--ion-color-light);font-size:.875rem}.import-row--created{border-left-color:var(--ion-color-success, #2dd36f)}.import-row--assigned{border-left-color:var(--ion-color-primary, #7026df)}.import-row--skipped{border-left-color:var(--ion-color-medium, #92949c)}.import-row--error{border-left-color:var(--ion-color-danger, #c0392b)}.import-row__email{font-weight:600;color:var(--ion-color-dark)}.import-row__status{text-align:right;color:var(--ion-color-medium-shade, #6b6e76)}.import-row__reason{grid-column:1 / -1;font-size:.75rem;color:var(--ion-color-danger, #c0392b)}\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: "ngmodule", type: ReactiveFormsModule }, { 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: IonTextarea, selector: "ion-textarea", inputs: ["autoGrow", "autocapitalize", "autofocus", "clearOnEdit", "color", "cols", "counter", "counterFormatter", "debounce", "disabled", "enterkeyhint", "errorText", "fill", "helperText", "inputmode", "label", "labelPlacement", "maxlength", "minlength", "mode", "name", "placeholder", "readonly", "required", "rows", "shape", "spellcheck", "value", "wrap"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { 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"] }, { kind: "component", type: RadioInputComponent, selector: "val-radio-input", inputs: ["props"] }, { kind: "component", type: CheckInputComponent, selector: "val-check-input", inputs: ["preset", "props"] }] }); }
|
|
50383
50437
|
}
|
|
50384
50438
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MemberImportModalComponent, decorators: [{
|
|
50385
50439
|
type: Component,
|
|
@@ -50392,13 +50446,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50392
50446
|
IonButton,
|
|
50393
50447
|
IonContent,
|
|
50394
50448
|
IonTextarea,
|
|
50395
|
-
IonCheckbox,
|
|
50396
50449
|
IonSpinner,
|
|
50397
50450
|
IonIcon,
|
|
50398
50451
|
DisplayComponent,
|
|
50399
50452
|
TextComponent,
|
|
50400
50453
|
TitleComponent,
|
|
50401
|
-
|
|
50454
|
+
RadioInputComponent,
|
|
50455
|
+
CheckInputComponent,
|
|
50402
50456
|
], template: `
|
|
50403
50457
|
<ion-header>
|
|
50404
50458
|
<ion-toolbar>
|
|
@@ -50412,10 +50466,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50412
50466
|
|
|
50413
50467
|
<ion-content class="ion-padding">
|
|
50414
50468
|
<val-display [props]="{ content: t('title'), size: 'small', color: 'dark' }" />
|
|
50469
|
+
<val-title [props]="{ content: t('subtitle'), size: 'large', color: '', bold: false }" />
|
|
50415
50470
|
|
|
50416
50471
|
@if (!results()) {
|
|
50417
|
-
<val-title [props]="{ content: t('hint'), size: 'large', color: '', bold: false }" />
|
|
50418
|
-
|
|
50419
50472
|
@if (rolesHint()) {
|
|
50420
50473
|
<val-text
|
|
50421
50474
|
class="import-roles-hint"
|
|
@@ -50423,40 +50476,79 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50423
50476
|
/>
|
|
50424
50477
|
}
|
|
50425
50478
|
|
|
50426
|
-
<!--
|
|
50427
|
-
|
|
50428
|
-
|
|
50429
|
-
<
|
|
50430
|
-
<
|
|
50431
|
-
|
|
50432
|
-
|
|
50433
|
-
|
|
50434
|
-
|
|
50479
|
+
<!-- Selector de método de importación -->
|
|
50480
|
+
<div class="import-section">
|
|
50481
|
+
<val-title [props]="{ content: t('importMethodLabel'), size: 'small', color: 'dark', bold: true }" />
|
|
50482
|
+
<div class="import-method-cards">
|
|
50483
|
+
<button
|
|
50484
|
+
type="button"
|
|
50485
|
+
class="import-method-card"
|
|
50486
|
+
[class.import-method-card--active]="importMode() === 'file'"
|
|
50487
|
+
(click)="importMode.set('file')"
|
|
50488
|
+
>
|
|
50489
|
+
<ion-icon name="cloud-upload-outline" class="import-method-card__icon" />
|
|
50490
|
+
<span class="import-method-card__title">{{ t('importMethodFile') }}</span>
|
|
50491
|
+
<span class="import-method-card__hint">{{ t('importMethodFileHint') }}</span>
|
|
50492
|
+
</button>
|
|
50493
|
+
<button
|
|
50494
|
+
type="button"
|
|
50495
|
+
class="import-method-card"
|
|
50496
|
+
[class.import-method-card--active]="importMode() === 'paste'"
|
|
50497
|
+
(click)="importMode.set('paste')"
|
|
50498
|
+
>
|
|
50499
|
+
<ion-icon name="document-text-outline" class="import-method-card__icon" />
|
|
50500
|
+
<span class="import-method-card__title">{{ t('importMethodPaste') }}</span>
|
|
50501
|
+
<span class="import-method-card__hint">{{ t('importMethodPasteHint') }}</span>
|
|
50502
|
+
</button>
|
|
50503
|
+
</div>
|
|
50435
50504
|
</div>
|
|
50436
50505
|
|
|
50437
|
-
|
|
50438
|
-
|
|
50439
|
-
|
|
50440
|
-
|
|
50506
|
+
<!-- Subida de archivo (label trick — gesto confiable en iOS/PWA) -->
|
|
50507
|
+
@if (importMode() === 'file') {
|
|
50508
|
+
<div class="import-section">
|
|
50509
|
+
<label class="import-upload__btn">
|
|
50510
|
+
<ion-icon name="cloud-upload-outline" />
|
|
50511
|
+
<span>{{ t('uploadFile') }}</span>
|
|
50512
|
+
<input type="file" accept=".csv,text/csv,text/plain" (change)="onFileSelected($event)" />
|
|
50513
|
+
</label>
|
|
50514
|
+
@if (fileName()) {
|
|
50515
|
+
<p class="import-file-loaded">
|
|
50516
|
+
{{ t('fileLoaded').replace('{name}', fileName()) }}
|
|
50517
|
+
</p>
|
|
50518
|
+
}
|
|
50519
|
+
</div>
|
|
50441
50520
|
}
|
|
50442
50521
|
|
|
50443
|
-
|
|
50444
|
-
|
|
50445
|
-
|
|
50446
|
-
|
|
50447
|
-
|
|
50448
|
-
|
|
50449
|
-
|
|
50522
|
+
<!-- Pegar CSV -->
|
|
50523
|
+
@if (importMode() === 'paste') {
|
|
50524
|
+
<div class="import-section">
|
|
50525
|
+
<ion-textarea
|
|
50526
|
+
class="import-csv"
|
|
50527
|
+
[autoGrow]="true"
|
|
50528
|
+
[rows]="6"
|
|
50529
|
+
[placeholder]="t('csvPlaceholder')"
|
|
50530
|
+
[(ngModel)]="csv"
|
|
50531
|
+
/>
|
|
50532
|
+
</div>
|
|
50533
|
+
}
|
|
50534
|
+
|
|
50535
|
+
<!-- Si hubo carga de archivo, mostrar textarea de solo lectura para revisar -->
|
|
50536
|
+
@if (importMode() === 'file' && csv) {
|
|
50537
|
+
<div class="import-section">
|
|
50538
|
+
<ion-textarea class="import-csv" [autoGrow]="true" [rows]="4" [readonly]="true" [value]="csv" />
|
|
50539
|
+
</div>
|
|
50540
|
+
}
|
|
50450
50541
|
|
|
50451
|
-
<!--
|
|
50452
|
-
<div class="
|
|
50453
|
-
<val-title [props]="{ content: t('onConflictLabel'), size: 'small', color: 'dark', bold:
|
|
50454
|
-
<val-
|
|
50542
|
+
<!-- Conflicto: radio buttons -->
|
|
50543
|
+
<div class="import-section">
|
|
50544
|
+
<val-title [props]="{ content: t('onConflictLabel'), size: 'small', color: 'dark', bold: true }" />
|
|
50545
|
+
<val-radio-input [props]="conflictRadioProps()" />
|
|
50455
50546
|
</div>
|
|
50456
50547
|
|
|
50457
|
-
|
|
50458
|
-
|
|
50459
|
-
|
|
50548
|
+
<!-- Activación: val-check-input -->
|
|
50549
|
+
<div class="import-section">
|
|
50550
|
+
<val-check-input [props]="sendActivationProps()" />
|
|
50551
|
+
</div>
|
|
50460
50552
|
|
|
50461
50553
|
@if (errorMsg()) {
|
|
50462
50554
|
<p class="import-error">{{ errorMsg() }}</p>
|
|
@@ -50493,7 +50585,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
50493
50585
|
</div>
|
|
50494
50586
|
}
|
|
50495
50587
|
</ion-content>
|
|
50496
|
-
`, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:
|
|
50588
|
+
`, styles: ["val-display{display:block;margin-bottom:4px}val-title{display:block;margin-bottom:8px}.import-roles-hint{display:block;margin-bottom:24px}.import-section{margin-bottom:28px}.import-section val-title{margin-bottom:10px}.import-method-cards{display:grid;grid-template-columns:1fr 1fr;gap:12px}.import-method-card{display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:14px 16px;border:1.5px solid var(--ion-color-light-shade, #d7d8da);border-radius:12px;background:var(--ion-color-light);cursor:pointer;text-align:left;transition:border-color .15s ease,background .15s ease}.import-method-card:hover{border-color:var(--ion-color-medium, #92949c)}.import-method-card--active{border-color:var(--ion-color-primary, #7026df);background:color-mix(in srgb,var(--ion-color-primary, #7026df) 6%,var(--ion-color-light))}.import-method-card__icon{font-size:1.375rem;color:var(--ion-color-medium-shade, #6b6e76);margin-bottom:4px}.import-method-card--active .import-method-card__icon{color:var(--ion-color-primary, #7026df)}.import-method-card__title{font-size:.875rem;font-weight:600;color:var(--ion-color-dark)}.import-method-card__hint{font-size:.75rem;color:var(--ion-color-medium, #92949c);line-height:1.3}.import-upload__btn{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;border:1px solid var(--ion-color-medium, #92949c);border-radius:10px;background:var(--ion-color-light);color:var(--ion-color-dark);font-size:.875rem;font-weight:600;cursor:pointer;transition:border-color .15s ease}.import-upload__btn:hover{border-color:var(--ion-color-primary, #7026df)}.import-upload__btn ion-icon{font-size:1.25rem}.import-upload__btn input[type=file]{display:none}.import-file-loaded{margin:10px 0 0;font-size:.8125rem;color:var(--ion-color-medium-shade, #6b6e76)}.import-csv{--background: var(--ion-color-light);border-radius:10px;font-family:monospace;font-size:.8125rem}.import-error{margin:0 0 12px;font-size:.875rem;color:var(--ion-color-danger, #c0392b)}.import-actions{margin-top:8px}.import-results{display:flex;flex-direction:column;gap:6px;margin:12px 0}.import-row{display:grid;grid-template-columns:1fr auto;grid-row-gap:2px;align-items:center;padding:10px 12px;border-radius:8px;border-left:3px solid var(--ion-color-medium);background:var(--ion-color-light);font-size:.875rem}.import-row--created{border-left-color:var(--ion-color-success, #2dd36f)}.import-row--assigned{border-left-color:var(--ion-color-primary, #7026df)}.import-row--skipped{border-left-color:var(--ion-color-medium, #92949c)}.import-row--error{border-left-color:var(--ion-color-danger, #c0392b)}.import-row__email{font-weight:600;color:var(--ion-color-dark)}.import-row__status{text-align:right;color:var(--ion-color-medium-shade, #6b6e76)}.import-row__reason{grid-column:1 / -1;font-size:.75rem;color:var(--ion-color-danger, #c0392b)}\n"] }]
|
|
50497
50589
|
}], ctorParameters: () => [], propDecorators: { orgId: [{
|
|
50498
50590
|
type: Input
|
|
50499
50591
|
}], availableRoles: [{
|