import {Component, EventEmitter, Input, OnInit, Output, inject } from '@angular/core'; import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { Subscription } from 'rxjs'; import { ABM_LIST_TYPE } from './enum/abm-list-type.enum'; import { AbmList } from './interface/abm-list.interface'; import { AbmListDefaultValues, AbmListOutput, ErrorValidator, FormTrackingEvent, SimpleDataOutput } from './interface'; import { CircleCheck, CircleMinus, CirclePlus, Pen, Trash2, User } from 'lucide-angular'; import { AddressEmitEvent } from './interface/address-emit-event'; import { Place } from './public-api'; import { InfoMessageInput } from './interface/info-message.interface'; import { SafeHtml } from '@angular/platform-browser'; @Component({ selector: 'kit-abm-list', templateUrl: './abm-list.component.html', styleUrls: ['../styles/index.scss'] }) export class AbmListComponent implements OnInit{ userIcon = User; iconTrash = Trash2; iconPencil = Pen; iconAdd = CircleCheck @Input() public maxForms: number = Infinity; @Input() public abmListDefaultValues!:AbmListDefaultValues[]; @Input() public abmListInputs:any; @Input() public abmListFormControls:any; @Input() public disabledRemove:boolean = false; @Input() public showAdd:boolean = true; @Input() public addFirstRow:boolean = true; @Input() public dataCurrency: any = []; @Input() public maskCurrency: any = [] @Input() public infoMessage!: InfoMessageInput; @Input() formValidationStates: boolean[] = []; @Input() addFormValid: boolean = false; @Input() defaultExpandedIndex: number | null = null; @Input() public descriptionDefault: string = '' @Input() public enumerateDescription:any; @Input() subtitle: SafeHtml[] = []; @Input() buttonName: string = 'Agregar'; @Output() public changeAbmList: EventEmitter = new EventEmitter(); @Output() public changeAddressEventEmit: EventEmitter = new EventEmitter(); @Output() changeSelectedLocationControl = new EventEmitter>(); @Output() public formTracking: EventEmitter = new EventEmitter(); @Output() dataOnly = new EventEmitter(); @Output() requestRemoveForm = new EventEmitter(); public viewButtonsAction: boolean = false public savedCardIndices: number[] = []; public activeExpansionIndex: number = 0; readonly circlePlus = CirclePlus; public readonly circleMinus = CircleMinus public abmDefaultValues:any[]=[]; public abmForm!: FormGroup public ABM_LIST_TYPE = ABM_LIST_TYPE; private subscriptionChangeForm: Subscription = new Subscription; activeCardIndex: number = 0; private selectedPlace: any; private selectedIndex: number | undefined; public readonly FORMS_ARRAY:string = 'formsArray' public formPrueba! : FormGroup private fb = inject(FormBuilder); expandedCardIndex: number[] = []; constructor() { } ngOnInit(): void { this.abmForm = this.fb.group({ [this.FORMS_ARRAY]: this.fb.array([]) }); if(this.abmListDefaultValues.length > 0){ this.setDefaultValues() } if(this.addFirstRow){ this.addForm(); } this.subscritionEvents(); this.expandedCardIndex = this.defaultExpandedIndex !== null ? [this.defaultExpandedIndex] : []; } get formsArray(): FormArray { return this.abmForm.get(this.FORMS_ARRAY) as FormArray; } get isValidFormWithoutEmpty(): boolean { let isValid: boolean = true; for (let index = 0; index < this.formsArray.controls.length; index++) { const form = this.formsArray.controls[index] as FormGroup; for(const controlId in form.controls) { const control = form.controls[controlId]; // Verificamos si el control tiene el validador required if (control.hasValidator(Validators.required)) { // Solo validamos si está vacío cuando el campo es required if (!control.value || control.value === '' || !control.valid) { isValid = false; } } } } return isValid; } get showAddButton(): boolean { return this.showAdd && this.formsArray.length < this.maxForms; } public isLastRow(index:number):boolean{ return this.formsArray.controls.length === index+1 } get canAddMore(): boolean { return this.showAdd && this.formsArray.length < this.maxForms; } getEnumerateDescription(index: number): string { return this.enumerateDescription[index] || this.descriptionDefault; } getValeuViewContact(index: number): SafeHtml { return this.subtitle[index] || ''; } getErrorDescription(errors: ErrorValidator[]): string { if (!errors || errors.length === 0) { return ''; } return errors[0].errorDescription; } isArray(value: any): boolean { return Array.isArray(value); } isFormValidByIndex(index: number): boolean { return this.formValidationStates && this.formValidationStates[index] === true; } isCardExpanded(index: number): boolean { return this.expandedCardIndex.includes(index); } // Expandimos la tarjeta al hacer clic en editar setActivePanel(index: number, event: MouseEvent): void { event.preventDefault(); if (!this.expandedCardIndex.includes(index)) { this.expandedCardIndex.push(index); } } public requestFormRemoval(index: number, event: MouseEvent): void { // Just emit the index of the form to be removed event.preventDefault() this.requestRemoveForm.emit(index); } public removeForm(index: number): void { let nextExpandIndex = index; if (index >= this.formsArray.length - 1) { nextExpandIndex = Math.max(0, index - 1); } const wasExpanded = this.isCardExpanded(index); // Eliminar formulario this.formsArray.removeAt(index); // Actualizar índices y arrays relacionados this.savedCardIndices = this.savedCardIndices.filter(i => i !== index) .map(i => i > index ? i - 1 : i); this.enumerateDescription.splice(index, 1); this.subtitle.splice(index, 1); this.expandedCardIndex = this.expandedCardIndex.filter(i => i !== index) .map(i => i > index ? i - 1 : i); if (wasExpanded && this.formsArray.length > 0) { const nextForm = this.formsArray.at(nextExpandIndex) as FormGroup; const isEmpty = this.isFormEmpty(nextForm); if (isEmpty && !this.expandedCardIndex.includes(nextExpandIndex)) { this.expandedCardIndex.push(nextExpandIndex); } } const trackingEvent: FormTrackingEvent = { action: 'remove', totalForms: this.formsArray.length, remainingForms: this.formsArray.controls.map((form, idx) => ({ index: idx, data: form.value })), removedIndex: index }; this.formTracking.emit(trackingEvent); this.emitFormArrayChange(); this.addFormValid = true; } private isFormEmpty(form: FormGroup): boolean { let isEmpty = true; Object.keys(form.controls).forEach(key => { const control = form.get(key); // Si el control tiene un valor if (control && control.value !== null && control.value !== '') { isEmpty = false; } }); return isEmpty; } public disabledRemoveButton(): boolean { return this.disabledRemove || (this.formsArray.length === 1 && this.addFirstRow); } public addForm(event?: MouseEvent): void { if (event) { event.preventDefault() } if (this.formsArray.length >= this.maxForms) { return; } const formGroup = this.fb.group(this.abmListFormControls); this.formsArray.push(formGroup) const newIndex = this.formsArray.length - 1; if (!this.expandedCardIndex.includes(newIndex)) { this.expandedCardIndex.push(newIndex); } const trackingEvent: FormTrackingEvent = { action: 'add', totalForms: this.formsArray.length, remainingForms: this.formsArray.controls.map((form, index) => ({ index, data: form.value })) }; this.formTracking.emit(trackingEvent); this.addFormValid = false; this.emitFormArrayChange() } setActiveCard(index: number): void { this.activeCardIndex = index; } private emitFormArrayChange(): void { const output: AbmListOutput = { data: this.formsArray.value, valid: this.isValidFormWithoutEmpty, formArray: this.formsArray, forms: this.formsArray.controls.map((form, index) => ({ index, form: form as FormGroup, controls: (form as FormGroup).controls })) }; this.changeAbmList.emit(output); } public getFormControl(abmListInput:AbmList,index:number){ return this.formsArray.controls[index].get(abmListInput.id) as FormControl } public getFormControlCurrency(controlId: string, index: number) { return this.formsArray.controls[index].get(controlId) as FormControl; } public getAbmListInput(abmListInput:AbmList){ abmListInput return {...abmListInput} } public getInputClass(abmListInput:AbmList){ return abmListInput.type === ABM_LIST_TYPE.DATE ? `${abmListInput.col} date-picker-flex` : abmListInput.col; } private get isValidFormWithoutEmptySub(): boolean { if (this.abmForm.status !== 'VALID') { return false; } return this.formsArray.controls.every((control: any ) => { const isEmpty = Object.values(control.value).every(value => value === null || value === '' || value === undefined ); return (isEmpty && control.pristine) || (!isEmpty && control.valid); }); } private setDefaultValues() { let index = 0; const output: AbmListOutput = { data: [], valid: true, formArray: this.formsArray, forms: [] }; for (const values of this.abmListDefaultValues) { this.addForm(); const form = this.formsArray.controls[index] as FormGroup; const { data, disabled } = values; for (const value in data) { if (form.get(value)) { form.controls[value].setValue(data[value]); if (disabled) { form.controls[value].disable(); } } } output.data.push(form.value); output.forms.push({ index, form: form, controls: form.controls }); output.index = index; this.savedCardIndices.push(index); index++; } this.addFormValid = true; output.valid = this.isValidFormWithoutEmptySub; this.changeAbmList.emit(output); } private subscritionEvents() { this.subscriptionChangeForm = this.abmForm.valueChanges.subscribe(() => { const changedIndex = this.formsArray.controls.findIndex((control) => control.dirty); if (changedIndex !== -1) { const control = this.formsArray.controls[changedIndex] as FormGroup; const output: AbmListOutput = { data: this.formsArray.value, valid: this.isValidFormWithoutEmptySub, formArray: this.formsArray, forms: [{ index: changedIndex, form: control, controls: control.controls }], index: changedIndex }; this.changeAbmList.emit(output); control.markAsPristine(); } }); } isCardSaved(index: number): boolean { return this.savedCardIndices.includes(index); } emitSave(event: MouseEvent, index: number) { event.preventDefault() this.expandedCardIndex = this.expandedCardIndex.filter(i => i !== index); if (!this.isCardSaved(index)) { this.savedCardIndices.push(index); } const allFormsValues = this.formsArray.value; // Filter only valid forms const validForms = allFormsValues.filter((_: any, i:number) => { const formGroup = this.formsArray.at(i); return formGroup && formGroup.valid; }); // Emit only valid forms data this.dataOnly.emit({ data: validForms }); } // Modificar el método eventEmitLocationSelected public eventEmitLocationSelected(place: Place, index: number, control: FormControl) { this.selectedPlace = place; this.selectedIndex = index; const data: AddressEmitEvent = { place: place, control: control, index: index } this.changeAddressEventEmit.emit(data); } public eventEmitLocationControl(control:FormControl) { this.changeSelectedLocationControl.emit(control) } }