瀏覽代碼

ACTUALIZACION modulo Administrador de formularios, subo identificador personalizado para campos

FREDY 3 月之前
父節點
當前提交
d72cc495dd

+ 2 - 0
Back/backendP-Educativa/app/Http/Controllers/FormController.php

@@ -279,5 +279,7 @@ public function getPublicado()
     ]);
 }
 
+
+
 }
 

+ 96 - 65
Front/src/app/modules/Administrador/pages/admin-form/admin-form.component.html

@@ -4,7 +4,8 @@
   </div>
 </div>
 
-<div class="container-main">
+<div class="container-main" [formGroup]="form">
+
   <!-- Panel de configuración general -->
   <div class="container-formConfig">
     <app-configuration-panel
@@ -18,14 +19,13 @@
       (rowsChange)="onRowsChange($event)"
       (columnsChange)="onColumnsChange($event)"
       (showTooltipChange)="onShowTooltipChange($event)"
-(generateClick)="saveOrUpdateForm()"
+      (generateClick)="saveOrUpdateForm()"
       (addTabClick)="addTab()"
       (removeTabClick)="removeTab()"
       (gridConfigChange)="buildGrid()"
       (tabDimensionsChange)="onTabDimensionsChange($event)">
     </app-configuration-panel>
-
-    <!-- Selector de tipos de campo (ahora dentro del panel de configuración general) -->
+    <!-- Selector de tipos de campo -->
     <div *ngIf="showElementSelector && selectedCell" class="selector">
       <p>Selecciona un tipo de campo para agregar en posición {{ selectedCell.row + 1 }}, {{ selectedCell.col + 1 }} - {{ currentTab?.name }}</p>
       <div class="element-buttons">
@@ -36,73 +36,104 @@
     </div>
   </div>
 
-  <!-- Vista previa del formulario -->
-  <div class="container-form">
-    <div class="title-form">Vista Previa del Formulario</div>
-
-    <!-- Navegación de tabs -->
-    <div class="tabs-navigation" *ngIf="tabs.length">
-      <div class="tab-header">
-        <div *ngFor="let tab of tabs; let i = index"
-             class="tab-item"
-             [class.active]="tab.active"
-             (click)="switchToTab(tab)">
-
-          <input
-            #tabInput
-            type="text"
-            class="tab-name-input"
-            [value]="tab.name"
-            (blur)="updateTabName(tab, tabInput.value)"
-            (keyup.enter)="updateTabName(tab, tabInput.value)">
-
-          <button *ngIf="tabs.length > 1"
-                  class="tab-close-btn"
-                  (click)="removeTab(); $event.stopPropagation()">
-            &times;
-          </button>
+  <!-- Contenedor principal con panel izquierdo y formulario -->
+  <div class="main-content">
+
+    <!-- Panel de propiedades del campo seleccionado - Movido a la izquierda -->
+
+
+    <!-- Vista previa del formulario -->
+    <div class="container-form">
+      <div class="title-form">Vista Previa del Formulario</div>
+
+      <!-- Navegación de tabs -->
+      <div class="tabs-navigation" *ngIf="tabs.length">
+        <div class="tab-header">
+          <div *ngFor="let tab of tabs; let i = index"
+               class="tab-item"
+               [class.active]="tab.active"
+               (click)="switchToTab(tab)">
+
+            <input
+              #tabInput
+              type="text"
+              class="tab-name-input"
+              [value]="tab.name"
+              (blur)="updateTabName(tab, tabInput.value)"
+              (keyup.enter)="updateTabName(tab, tabInput.value)">
+
+            <button *ngIf="tabs.length > 1"
+                    class="tab-close-btn"
+                    (click)="removeTab(); $event.stopPropagation()">
+              &times;
+            </button>
+          </div>
         </div>
       </div>
-    </div>
 
-    <!-- Cuadrícula de campos -->
-    <div class="form-grid" [style.grid-template-columns]="'repeat(' + columns + ', 1fr)'">
-      <div *ngFor="let row of grid; let i = index" class="grid-row">
-        <div *ngFor="let cell of row; let j = index"
-             class="grid-cell"
-             [class.selected]="cell.selected"
-             [class.has-element]="cell.element"
-             (click)="selectCell(cell)"
-             (dblclick)="selectElementForEditing(cell)">
-
-          <div *ngIf="cell.element" class="cell-element">
-            <label>{{ cell.element.label }}</label>
-
-            <ng-container [ngSwitch]="cell.element.type">
-              <input *ngSwitchCase="'text'" type="text" class="form__input" [placeholder]="cell.element.placeholder">
-              <input *ngSwitchCase="'number'" type="number" class="form__input" [placeholder]="cell.element.placeholder">
-              <input *ngSwitchCase="'email'" type="email" class="form__input" [placeholder]="cell.element.placeholder">
-              <input *ngSwitchCase="'date'" type="date" class="form__input">
-              <textarea *ngSwitchCase="'textarea'" class="form__textarea" [placeholder]="cell.element.placeholder"></textarea>
-              <select *ngSwitchCase="'select'" class="form__select">
-                <option *ngFor="let option of cell.element.options">{{ option }}</option>
-              </select>
-              <div *ngSwitchCase="'radio'" class="form__radio-group">
-                <label *ngFor="let option of cell.element.options">
-                  <input type="radio" [name]="cell.element.name" [value]="option"> {{ option }}
+      <!-- Cuadrícula de campos -->
+      <div class="form-grid" [style.grid-template-columns]="'repeat(' + columns + ', 1fr)'">
+        <div *ngFor="let row of grid; let i = index" class="grid-row">
+          <div *ngFor="let cell of row; let j = index"
+               class="grid-cell"
+               [class.selected]="cell.selected"
+               [class.has-element]="cell.element"
+               (click)="selectCell(cell)"
+               (dblclick)="selectElementForEditing(cell)">
+
+            <div *ngIf="cell.element" class="cell-element">
+              <label>{{ cell.element.label }}</label>
+
+              <ng-container [ngSwitch]="cell.element.type">
+                <input *ngSwitchCase="'text'" type="text" class="form__input"
+                  [placeholder]="cell.element.placeholder"
+                  [formControlName]="getSafeControlName(cell, i, j)">
+
+                <input *ngSwitchCase="'number'" type="number" class="form__input"
+                  [placeholder]="cell.element.placeholder"
+                  [formControlName]="getSafeControlName(cell, i, j)">
+
+                <input *ngSwitchCase="'email'" type="email" class="form__input"
+                  [placeholder]="cell.element.placeholder"
+                  [formControlName]="getSafeControlName(cell, i, j)">
+
+                <input *ngSwitchCase="'date'" type="date" class="form__input"
+                  [formControlName]="getSafeControlName(cell, i, j)">
+
+                <textarea *ngSwitchCase="'textarea'" class="form__textarea"
+                  [placeholder]="cell.element.placeholder"
+                  [formControlName]="getSafeControlName(cell, i, j)"></textarea>
+
+                <select *ngSwitchCase="'select'" class="form__select"
+                  [formControlName]="getSafeControlName(cell, i, j)">
+                  <option *ngFor="let option of cell.element.options">{{ option }}</option>
+                </select>
+
+                <div *ngSwitchCase="'radio'" class="form__radio-group">
+                  <label *ngFor="let option of cell.element.options">
+                    <input type="radio"
+                      [formControlName]="getSafeControlName(cell, i, j)"
+                      [value]="option"
+                      [formControlName]="getSafeControlName(cell, i, j)">
+                    {{ option }}
+                  </label>
+                </div>
+
+                <label *ngSwitchCase="'checkbox'" class="form__checkbox">
+                  <input type="checkbox" [formControlName]="getSafeControlName(cell, i, j)">
+                  {{ cell.element.label }}
                 </label>
-              </div>
-              <label *ngSwitchCase="'checkbox'" class="form__checkbox">
-                <input type="checkbox"> {{ cell.element.label }}
-              </label>
-            </ng-container>
+              </ng-container>
 
-            <button class="remove-btn" (click)="removeElementFromCell(cell); $event.stopPropagation()">&times;</button>
-          </div>
+              <button class="remove-btn" (click)="removeElementFromCell(cell); $event.stopPropagation()">
+                &times;
+              </button>
+            </div>
 
-          <div *ngIf="!cell.element" class="cell-placeholder">
-            <span>Posición {{ i + 1 }},{{ j + 1 }}</span>
-            <small>Haz clic para agregar un campo</small>
+            <div *ngIf="!cell.element" class="cell-placeholder">
+              <span>Posición {{ i + 1 }},{{ j + 1 }}</span>
+              <small>Haz clic para agregar un campo</small>
+            </div>
           </div>
         </div>
       </div>

+ 106 - 43
Front/src/app/modules/Administrador/pages/admin-form/admin-form.component.ts

@@ -4,6 +4,8 @@ import { EnviarInfoService } from '../../services/enviar-info.service';
 import { FormService } from '../../services/FormService.service';
 import { ActivatedRoute, Router } from '@angular/router';
 import { FormArray } from '@angular/forms';
+import { FormBuilder, FormGroup, FormControl } from '@angular/forms';
+
 interface GridCell {
   row: number;
   col: number;
@@ -81,15 +83,16 @@ export class AdminFormComponent implements OnInit {
   ];
   formData: any;
   form: any;
-  fb: any;
 
-  constructor(
-    private _enviarInfo: EnviarInfoService,
-    private formApiService: FormApiService,
-    private formService: FormService,
-    private route: ActivatedRoute,
-     private router: Router
-  ) {}
+constructor(
+  private _enviarInfo: EnviarInfoService,
+  private formApiService: FormApiService,
+  private formService: FormService,
+  private route: ActivatedRoute,
+  private router: Router,
+  private fb: FormBuilder
+) {}
+
 
   ngOnInit() {
     this._enviarInfo.currentTextColor.subscribe(color => this.textColor = color);
@@ -104,6 +107,32 @@ export class AdminFormComponent implements OnInit {
   });
     // this.loadSavedForms();
   }
+  initializeForm(): void {
+  const formControls: { [key: string]: FormControl } = {};
+
+  for (const tab of this.tabs) {
+    for (let rowIndex = 0; rowIndex < tab.grid.length; rowIndex++) {
+      const row = tab.grid[rowIndex];
+      for (let colIndex = 0; colIndex < row.length; colIndex++) {
+        const cell = row[colIndex];
+        if (cell?.element) {
+          // Usar tab directamente aquí ya que estamos iterando sobre this.tabs
+          const controlName = this.getControlName(tab, { ...cell, row: rowIndex, col: colIndex });
+          formControls[controlName] = new FormControl('');
+        }
+      }
+    }
+  }
+
+  this.form = this.fb.group(formControls);
+}
+getSafeControlName(cell: any, rowIndex: number, colIndex: number): string {
+  if (!this.currentTab) {
+    return `campo_${rowIndex}_${colIndex}`;
+  }
+  return this.getControlName(this.currentTab, { ...cell, row: rowIndex, col: colIndex });
+}
+
 
   // MÉTODOS PARA MANEJAR LA API
 
@@ -247,6 +276,7 @@ export class AdminFormComponent implements OnInit {
       });
 
       this.switchToTab(this.tabs[0]);
+        this.initializeForm();
     } else {
       this.addTab();
     }
@@ -439,43 +469,74 @@ export class AdminFormComponent implements OnInit {
     this.editingElement = null;
   }
 
-  addElementToCell(template: FormElement) {
-    if (!this.selectedCell || this.selectedCell.element) {
-      alert('Esta celda ya tiene un campo. Elimínalo primero.');
-      return;
-    }
 
-    const element = { ...template };
-    element.id = `${this.currentTab?.id}_campo_${this.selectedCell.row}_${this.selectedCell.col}`;
-    element.name = element.id;
-
-    switch (element.type) {
-      case 'email':
-        Object.assign(element, {
-          required: true,
-          pattern: '^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$',
-          placeholder: 'ejemplo@correo.com'
-        });
-        break;
-      case 'number':
-        Object.assign(element, { required: true, min: 1, max: 100 });
-        break;
-      case 'text':
-        Object.assign(element, {
-          required: true,
-          min: 1,
-          max: 20,
-          pattern: '^\\b(\\w+\\b\\s*){1,20}$'
-        });
-        break;
-    }
 
-    this.selectedCell.element = element;
-    this.editingElement = element;
-    this.showElementSelector = false;
-    this.updateCurrentTabGrid();
+addElementToCell(template: FormElement) {
+  if (!this.selectedCell || this.selectedCell.element) {
+    alert('Esta celda ya tiene un campo. Elimínalo primero.');
+    return;
   }
 
+  // Solicita un "nombre clave" legible para el input, ejemplo: "nombre_alumno"
+  let nombreClave = prompt('Ingresa el identificador único para este campo (ej. nombre_alumno):');
+  if (!nombreClave) {
+    alert('Debes ingresar un identificador válido.');
+    return;
+  }
+
+  // Limpia espacios y pasa a minúsculas para usar como key
+  nombreClave = nombreClave.trim().toLowerCase().replace(/\s+/g, '_');
+
+  const element = { ...template };
+
+  element.id = `${this.currentTab?.id}_${nombreClave}`;
+  element.name = nombreClave;          // <--- Aquí asignamos el key legible
+  element.label = this.capitalizeWords(nombreClave.replace(/_/g, ' ')); // etiqueta legible: "Nombre Alumno"
+
+  // Aplica validaciones según tipo, igual que antes
+  switch (element.type) {
+    case 'email':
+      Object.assign(element, {
+        required: true,
+        pattern: '^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$',
+        placeholder: 'ejemplo@correo.com'
+      });
+      break;
+    case 'number':
+      Object.assign(element, { required: true, min: 1, max: 100 });
+      break;
+    case 'text':
+      Object.assign(element, {
+        required: true,
+        min: 1,
+        max: 20,
+        pattern: '^\\b(\\w+\\b\\s*){1,20}$'
+      });
+      break;
+  }
+
+  this.selectedCell.element = element;
+  this.editingElement = element;
+  this.showElementSelector = false;
+  this.updateCurrentTabGrid();
+}
+getControlName(tab: Tab | null, cell: any): string {
+  const row = cell.row ?? 0;
+  const col = cell.col ?? 0;
+
+  const rawName = cell.element?.name;
+  const cleaned = rawName?.trim().replace(/\s+/g, '_').toLowerCase();
+
+  return cleaned && cleaned !== '' ? cleaned : `campo_${row}_${col}`;
+}
+
+
+// Función para capitalizar palabras para label bonito
+capitalizeWords(text: string): string {
+  return text.replace(/\b\w/g, c => c.toUpperCase());
+}
+
+
   updateCurrentTabGrid() {
     if (this.currentTab) this.currentTab.grid = [...this.grid];
   }
@@ -502,15 +563,17 @@ export class AdminFormComponent implements OnInit {
       this.updateCurrentTabGrid();
     }
   }
- cargarFormulario(id: number): void {
+cargarFormulario(id: number): void {
   this.formService.getFormById(id).subscribe(data => {
     this.formData = data;
     console.log('Form a editar:', this.formData);
 
-    // Llama al método que reconstruye el formulario visual
     this.loadFormConfiguration(this.formData.form.configuration);
     this.formTitle = this.formData.form.title;
     this.currentFormId = this.formData.form.id;
+
+    // Inicializa el formulario reactivo basado en los campos
+    this.initializeForm();
   });
 }
 

+ 3 - 11
Front/src/app/modules/Administrador/services/FormService.service.ts

@@ -58,20 +58,12 @@ publishForm(id: number): Observable<any> {
   });
 }
 
-
-  // ✅ Enviar una respuesta de formulario
-  sendFormResponse(formId: number, data: any): Observable<any> {
-    return this.http.post(`${this.URL}/${formId}/responses`, data, {
+enviarRespuestas(respuestas: any) {
+  return this.http.post('/api/respuestas', respuestas,{
       headers: this.getHeaders()
     });
-  }
+}
 
-  // ✅ Obtener respuestas de un formulario
-  getResponses(formId: number): Observable<any> {
-    return this.http.get(`${this.URL}/${formId}/responses`, {
-      headers: this.getHeaders()
-    });
-  }
   deleteForm(id: number): Observable<any> {
   return this.http.delete(`${this.URL}/forms/${id}`, {
     headers: this.getHeaders()