Saltar a contenido

Modelo Client - CRUD

Crearemos las peticiones desde el frontend para el modelo Client.

Para ello realizamos paso a paso para que relliquemos lo mismo a los demas modelos

1. Realizamos el Modelo Client

Creamos el modelo en src/app/models/client.ts

export interface ClientI {
  id?: number;
  name: string;
  address: string;
  phone: string;
  email: string;
  password: string;
  status: "ACTIVE" | "INACTIVE";
}


export interface ClientResponseI {
  id?: number;
  name: string;
  address: string;
  phone: string;
  email: string;
}

2. Realizamos el Servicio Client

Creamos el modelo en src/app/services/client.ts

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, BehaviorSubject, tap } from 'rxjs';
import { ClientI} from '../models/client';
import { AuthService } from './auth.service';

@Injectable({
  providedIn: 'root'
})
export class ClientService {
  private baseUrl = 'http://localhost:4000/api/clients';
  private clientsSubject = new BehaviorSubject<ClientI[]>([]);
  public clients$ = this.clientsSubject.asObservable();

  constructor(
    private http: HttpClient,
    private authService: AuthService
  ) {}

  private getHeaders(): HttpHeaders {
    let headers = new HttpHeaders();
    const token = this.authService.getToken();
    if (token) {
      headers = headers.set('Authorization', `Bearer ${token}`);
    }
    return headers;
  }


  getAllClients(): Observable<ClientI[]> {
    return this.http.get<ClientI[]>(this.baseUrl, { headers: this.getHeaders() })
    // .pipe(
    //   tap(response => {
    //       // console.log('Fetched clients:', response);
    //     })
    // )
    ;
  }

  getClientById(id: number): Observable<ClientI> {
    return this.http.get<ClientI>(`${this.baseUrl}/${id}`, { headers: this.getHeaders() });
  }

  createClient(client: ClientI): Observable<ClientI> {
    return this.http.post<ClientI>(this.baseUrl, client, { headers: this.getHeaders() });
  }

  updateClient(id: number, client: ClientI): Observable<ClientI> {
    return this.http.patch<ClientI>(`${this.baseUrl}/${id}`, client, { headers: this.getHeaders() });
  }

  deleteClient(id: number): Observable<void> {
    return this.http.delete<void>(`${this.baseUrl}/${id}`, { headers: this.getHeaders() });
  }

  deleteClientLogic(id: number): Observable<void> {
    return this.http.delete<void>(`${this.baseUrl}/${id}/logic`, { headers: this.getHeaders() });
  }

  // Método para actualizar el estado local de clientes
  updateLocalClients(clients: ClientI[]): void {
    this.clientsSubject.next(clients);
  }

  refreshClients(): void {
    this.getAllClients().subscribe(clients => {
      this.clientsSubject.next(clients);
    });
  }
}

2. Enlazamos con el componente Client

a. Iniciamos con mostrar todo (getall.ts y getall.html)

Nota:

Como vamos a traer los datos que son enviados desde el controlador del backend. se requiere antes revisar el codigomde dicho controlador:

client.controller.ts

public async getAllClients(req: Request, res: Response) {
    try {
      const clients: ClientI[] = await Client.findAll({
        where: { status: 'ACTIVE' },
      });
      res.status(200).json(clients );
    } catch (error) {
      res.status(500).json({ error: "Error fetching clients" });
    }
  }

src/app/components/client/getall.ts

import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { TableModule } from 'primeng/table';
import { CommonModule } from '@angular/common';
import { ClientI } from '../../../models/client';
import { ButtonModule } from 'primeng/button';
import { RouterModule } from '@angular/router';
import { ClientService } from '../../../services/client.service';
import { ConfirmDialogModule } from 'primeng/confirmdialog';
import { ConfirmationService, MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';

@Component({
  selector: 'app-getall',
  imports: [TableModule, CommonModule, ButtonModule, RouterModule, ConfirmDialogModule, ToastModule],
  templateUrl: './getall.html',
  styleUrl: './getall.css',
  encapsulation: ViewEncapsulation.None,
  providers: [ConfirmationService, MessageService]
})
export class Getall implements OnInit {
  clients: ClientI[] = [];
  loading: boolean = false;

  constructor(
    private clientService: ClientService,
    private confirmationService: ConfirmationService,
    private messageService: MessageService
  ) {}

  ngOnInit(): void {
    this.loadClients();
  }

  loadClients(): void {
    this.loading = true;
    this.clientService.getAllClients().subscribe({
      next: (clients) => {
        this.clients = clients;
        this.clientService.updateLocalClients(clients);
        this.loading = false;
      },
      error: (error) => {
        console.error('Error loading clients:', error);
        this.messageService.add({
          severity: 'error',
          summary: 'Error',
          detail: 'Error al cargar los clientes'
        });
        this.loading = false;
      }
    });
  }

  deleteClient(client: ClientI): void {
    this.confirmationService.confirm({
      message: `¿Está seguro de eliminar el cliente ${client.name}?`,
      header: 'Confirmar eliminación',
      icon: 'pi pi-exclamation-triangle',
      accept: () => {
        if (client.id) {
          this.clientService.deleteClient(client.id).subscribe({
            next: () => {
              this.messageService.add({
                severity: 'success',
                summary: 'Éxito',
                detail: 'Cliente eliminado correctamente'
              });
              this.loadClients();
            },
            error: (error) => {
              console.error('Error deleting client:', error);
              this.messageService.add({
                severity: 'error',
                summary: 'Error',
                detail: 'Error al eliminar el cliente'
              });
            }
          });
        }
      }
    });
  }
}

src/app/components/client/getall.html

<div class="card p-8">
    <div class="flex justify-between items-center mb-4">
        <h2 class="text-2xl font-bold">Gestión de Clientes</h2>
        <button
            pButton
            type="button"
            class="p-button p-component bg-blue-500 hover:bg-blue-600 text-white"
            icon="pi pi-plus"
            label="Nuevo Cliente"
            [routerLink]="['/clients/new']"
        >
        </button>
    </div>

    <p-table 
        [value]="clients" 
        [loading]="loading"
        stripedRows 
        [tableStyle]="{'min-width': '50rem'}"
        [paginator]="true"
        [rows]="10"
        [showCurrentPageReport]="true"
        currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} registros"
    >
        <ng-template #header>
            <tr>
                <th class="text-left">ID</th>
                <th class="text-left">Nombre</th>
                <th class="text-left">Dirección</th>
                <th class="text-left">Teléfono</th>
                <th class="text-left">Email</th>
                <th class="text-left">Estado</th>
                <th class="text-center">Acciones</th>
            </tr>
        </ng-template>
        <ng-template #body let-client>
            <tr>
                <td>{{client.id}}</td>
                <td>{{client.name}}</td>
                <td>{{client.address}}</td>
                <td>{{client.phone}}</td>
                <td>{{client.email}}</td>
                <td>
                    <span 
                        class="px-2 py-1 rounded text-xs font-semibold"
                        [class]="client.status === 'ACTIVE' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'"
                    >
                        {{client.status}}
                    </span>
                </td>
                <td class="text-center">
                    <div class="flex justify-center gap-2">
                        <button
                            pButton
                            type="button"
                            icon="pi pi-pencil"
                            class="p-button-rounded p-button-text p-button-warning"
                            [routerLink]="['/clients/edit', client.id]"
                            pTooltip="Editar"
                        ></button>
                        <button
                            pButton
                            type="button"
                            icon="pi pi-trash"
                            class="p-button-rounded p-button-text p-button-danger"
                            (click)="deleteClient(client)"
                            pTooltip="Eliminar"
                        ></button>
                    </div>
                </td>
            </tr>
        </ng-template>
    </p-table>
</div>

<p-confirmDialog></p-confirmDialog>
<p-toast></p-toast>

Corfirmamos las rutas en Confimamos en src/app.routes.ts

import { Routes } from '@angular/router';
import { Login } from './components/auth/login/login';
import { Register } from './components/auth/register/register';
import { AuthGuard } from './guards/authguard';

// Client components with aliases
import { Getall as ClientGetall } from './components/client/getall/getall';
import { Create as ClientCreate } from './components/client/create/create';
import { Update as ClientUpdate } from './components/client/update/update';
import { Delete as ClientDelete } from './components/client/delete/delete';

// Product components with aliases
import { Getall as ProductGetall } from './components/product/getall/getall';
import { Create as ProductCreate } from './components/product/create/create';
import { Update as ProductUpdate } from './components/product/update/update';
import { Delete as ProductDelete } from './components/product/delete/delete';

// Sale components with aliases
import { Getall as SaleGetall } from './components/sale/getall/getall';
import { Create as SaleCreate } from './components/sale/create/create';
import { Update as SaleSaleUpdate } from './components/sale/update/update';
import { Delete as SaleDelete } from './components/sale/delete/delete';

export const routes: Routes = [
    { 
        path: '', 
        redirectTo: '/login', 
        pathMatch: 'full' 
    },
    {
        path: "login",
        component: Login
    },
    {
        path: "register",
        component: Register
    },
    {
        path: "clients",
        component: ClientGetall,
        canActivate: [AuthGuard]
    },
    {
        path: "clients/new",
        component: ClientCreate,
        canActivate: [AuthGuard]
    },
    {
        path: "clients/edit/:id",
        component: ClientUpdate,
        canActivate: [AuthGuard]
    },
    {
        path: "clients/delete/:id",
        component: ClientDelete,
        canActivate: [AuthGuard]
    },
    {
        path: "products",
        component: ProductGetall,
        canActivate: [AuthGuard]
    },
    {
        path: "products/new",
        component: ProductCreate,
        canActivate: [AuthGuard]
    },
    {
        path: "products/edit/:id",
        component: ProductUpdate,
        canActivate: [AuthGuard]
    },
    {
        path: "products/delete/:id",
        component: ProductDelete,
        canActivate: [AuthGuard]
    },
    {
        path: "sales",
        component: SaleGetall,
        canActivate: [AuthGuard]
    },
    {
        path: "sales/new",
        component: SaleCreate,
        canActivate: [AuthGuard]
    },
    {
        path: "sales/edit/:id",
        component: SaleSaleUpdate,
        canActivate: [AuthGuard]
    },
    {
        path: "sales/delete/:id",
        component: SaleDelete,
        canActivate: [AuthGuard]
    },
    {
        path: "**",
        redirectTo: "/login",
        pathMatch: "full"
    }
];

Confimamos en src/app.config.ts

import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeuix/themes/aura';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideAnimationsAsync(),
    provideHttpClient(),
        providePrimeNG({
            theme: {
                preset: Aura
            }
        })
  ]
};

Al ejecutar en el terminal ng serve

Si no te aparecen los iconos en las acciones, es necesario instlalar PrimeIcons

En el terminal

npm install primeicons

Y en el archivo src/styles.css adicional al final

@import "primeicons/primeicons.css";

b. Create (create.ts y create.html)

Nota:

Como vamos a traer los datos que son enviados desde el controlador del backend. se requiere antes revisar el codigomde dicho controlador:

client.controller.ts

public async createClient(req: Request, res: Response) {
    const { id, name, address, phone, email, password, status } = req.body;
    try {
      let body: ClientI = {
        name,
        address,
        phone,
        email,
        password,
        status,
      };

      const newClient = await Client.create({ ...body });
      res.status(201).json(newClient);
    } catch (error: any) {
      res.status(400).json({ error: error.message });
    }
  }

src/app/components/client/create.ts

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { Select } from 'primeng/select';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { ClientService } from '../../../services/client.service';

@Component({
  selector: 'app-create',
  imports: [CommonModule, ReactiveFormsModule, ButtonModule, InputTextModule, Select, ToastModule],
  templateUrl: './create.html',
  styleUrl: './create.css',
  providers: [MessageService]
})
export class Create {
  form: FormGroup;
  loading: boolean = false;
  statusOptions = [
    { label: 'Activo', value: 'ACTIVE' },
    { label: 'Inactivo', value: 'INACTIVE' }
  ];

  constructor(
    private fb: FormBuilder,
    private router: Router,
    private clientService: ClientService,
    private messageService: MessageService
  ) {
    this.form = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      address: ['', [Validators.required, Validators.minLength(5)]],
      phone: ['', [Validators.required, Validators.pattern(/^\d{10,15}$/)]],
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]],
      status: ['ACTIVE', Validators.required]
    });
  }

  submit(): void {
    if (this.form.valid) {
      this.loading = true;
      const clientData = this.form.value;

      this.clientService.createClient(clientData).subscribe({
        next: (response) => {
          this.messageService.add({
            severity: 'success',
            summary: 'Éxito',
            detail: 'Cliente creado correctamente'
          });
          setTimeout(() => {
            this.router.navigate(['/clients']);
          }, 1000);
        },
        error: (error) => {
          console.error('Error creating client:', error);
          this.messageService.add({
            severity: 'error',
            summary: 'Error',
            detail: 'Error al crear el cliente'
          });
          this.loading = false;
        }
      });
    } else {
      this.markFormGroupTouched();
      this.messageService.add({
        severity: 'warn',
        summary: 'Advertencia',
        detail: 'Por favor complete todos los campos requeridos'
      });
    }
  }

  cancelar(): void {
    this.router.navigate(['/clients']);
  }

  private markFormGroupTouched(): void {
    Object.keys(this.form.controls).forEach(key => {
      this.form.get(key)?.markAsTouched();
    });
  }

  getFieldError(fieldName: string): string {
    const field = this.form.get(fieldName);
    if (field?.errors && field?.touched) {
      if (field.errors['required']) return `${fieldName} es requerido`;
      if (field.errors['email']) return 'Email no válido';
      if (field.errors['minlength']) return `${fieldName} debe tener al menos ${field.errors['minlength'].requiredLength} caracteres`;
      if (field.errors['pattern']) return 'Formato no válido';
    }
    return '';
  }
}

src/app/components/client/create.html

<div class="max-w-4xl mx-auto p-6">
  <div class="bg-white rounded-lg shadow-lg p-8">
    <h2 class="text-2xl font-bold mb-6 text-gray-800">Crear Nuevo Cliente</h2>

    <form [formGroup]="form" (ngSubmit)="submit()">
      <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Nombre *</label>
          <input 
            pInputText 
            formControlName="name" 
            class="w-full"
            [class.ng-invalid]="form.get('name')?.invalid && form.get('name')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('name')">
            {{ getFieldError('name') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Dirección *</label>
          <input 
            pInputText 
            formControlName="address" 
            class="w-full"
            [class.ng-invalid]="form.get('address')?.invalid && form.get('address')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('address')">
            {{ getFieldError('address') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Teléfono *</label>
          <input 
            pInputText 
            formControlName="phone" 
            class="w-full"
            placeholder="1234567890"
            [class.ng-invalid]="form.get('phone')?.invalid && form.get('phone')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('phone')">
            {{ getFieldError('phone') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Email *</label>
          <input 
            pInputText 
            type="email"
            formControlName="email" 
            class="w-full"
            [class.ng-invalid]="form.get('email')?.invalid && form.get('email')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('email')">
            {{ getFieldError('email') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Contraseña *</label>
          <input 
            pInputText 
            type="password" 
            formControlName="password" 
            class="w-full"
            [class.ng-invalid]="form.get('password')?.invalid && form.get('password')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('password')">
            {{ getFieldError('password') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Estado *</label>
          <p-select
            formControlName="status"
            [options]="statusOptions"
            placeholder="Seleccionar estado"
            class="w-full"
          ></p-select>
        </div>
      </div>

      <div class="flex justify-center mt-8">
        <div class="bg-gray-50 rounded-lg p-4 flex gap-4 items-center">
          <button
            pButton
            type="submit"
            class="p-button bg-blue-500 hover:bg-blue-600 text-white"
            label="Guardar"
            icon="pi pi-save"
            [loading]="loading"
            [disabled]="loading"
          ></button>
          <button
            pButton
            type="button"
            class="p-button p-button-secondary"
            label="Cancelar"
            icon="pi pi-times"
            (click)="cancelar()"
            [disabled]="loading"
          ></button>
        </div>
      </div>
    </form>
  </div>
</div>

<p-toast></p-toast>

c. Create (update.ts y update.html)

Nota:

Como vamos a traer los datos que son enviados desde el controlador del backend. se requiere antes revisar el codigomde dicho controlador:

client.controller.ts

// Get a client by ID

  public async getClientById(req: Request, res: Response) {
    try {
      const { id: pk } = req.params;
      const client = await Client.findOne({
        where: { 
          id: pk, 
          status: 'ACTIVE' },
      });
      if (client) {
        res.status(200).json(client);
      } else {
        res.status(404).json({ error: "Client not found or inactive" });
      }
    } catch (error) {
      res.status(500).json({ error: "Error fetching client" });
    }
  }



// Update a client
public async updateClient(req: Request, res: Response) {
    const { id: pk } = req.params;
    const { id, name, address, phone, email, password, status } = req.body;
    try {
      let body: ClientI = {
        name,
        address,
        phone,
        email,
        password,
        status,
      };

      const clientExist = await Client.findOne({
        where: { 
          id: pk, 
          status: 'ACTIVE' },
      });

      if (clientExist) {
        await clientExist.update(body, {
          where: { id: pk },
        });
        res.status(200).json(clientExist);
      } else {
        res.status(404).json({ error: "Client not found or inactive" });
      }
    } catch (error: any) {
      res.status(400).json({ error: error.message });
    }
  }

src/app/components/client/update.ts

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { Select } from 'primeng/select';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { ClientService } from '../../../services/client.service';
import { ClientI } from '../../../models/client';

@Component({
  selector: 'app-update',
  imports: [CommonModule, ReactiveFormsModule, ButtonModule, InputTextModule, Select, ToastModule],
  templateUrl: './update.html',
  styleUrl: './update.css',
  providers: [MessageService]
})
export class Update implements OnInit {
  form: FormGroup;
  loading: boolean = false;
  clientId: number = 0;
  statusOptions = [
    { label: 'Activo', value: 'ACTIVE' },
    { label: 'Inactivo', value: 'INACTIVE' }
  ];

  constructor(
    private fb: FormBuilder,
    private router: Router,
    private route: ActivatedRoute,
    private clientService: ClientService,
    private messageService: MessageService
  ) {
    this.form = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(2)]],
      address: ['', [Validators.required, Validators.minLength(5)]],
      phone: ['', [Validators.required, Validators.pattern(/^\d{10,15}$/)]],
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]],
      status: ['ACTIVE', Validators.required]
    });
  }

  ngOnInit(): void {
    const id = this.route.snapshot.paramMap.get('id');
    if (id) {
      this.clientId = parseInt(id);
      this.loadClient();
    }
  }

  loadClient(): void {
    this.loading = true;
    this.clientService.getClientById(this.clientId).subscribe({
      next: (client) => {
        this.form.patchValue(client);
        this.loading = false;
      },
      error: (error) => {
        console.error('Error loading client:', error);
        this.messageService.add({
          severity: 'error',
          summary: 'Error',
          detail: 'Error al cargar el cliente'
        });
        this.loading = false;
      }
    });
  }

   submit(): void {
    if (this.form.valid) {
      this.loading = true;
      const clientData = this.form.value;

      this.clientService.updateClient(this.clientId, clientData).subscribe({
        next: (response) => {
          this.messageService.add({
            severity: 'success',
            summary: 'Éxito',
            detail: 'Cliente actualizado correctamente'
          });
          setTimeout(() => {
            this.router.navigate(['/clients']);
          }, 1000);
        },
        error: (error) => {
          console.error('Error updating client:', error);
          this.messageService.add({
            severity: 'error',
            summary: 'Error',
            detail: 'Error al actualizar el cliente'
          });
          this.loading = false;
        }
      });
    } else {
      this.markFormGroupTouched();
      this.messageService.add({
        severity: 'warn',
        summary: 'Advertencia',
        detail: 'Por favor complete todos los campos requeridos'
      });
    }
  }

  cancelar(): void {
    this.router.navigate(['/clients']);
  }

  private markFormGroupTouched(): void {
    Object.keys(this.form.controls).forEach(key => {
      this.form.get(key)?.markAsTouched();
    });
  }

  getFieldError(fieldName: string): string {
    const field = this.form.get(fieldName);
    if (field?.errors && field?.touched) {
      if (field.errors['required']) return `${fieldName} es requerido`;
      if (field.errors['email']) return 'Email no válido';
      if (field.errors['minlength']) return `${fieldName} debe tener al menos ${field.errors['minlength'].requiredLength} caracteres`;
      if (field.errors['pattern']) return 'Formato no válido';
    }
    return '';
  }
}

src/app/components/client/update.html

<div class="max-w-4xl mx-auto p-6">
  <div class="bg-white rounded-lg shadow-lg p-8">
    <h2 class="text-2xl font-bold mb-6 text-gray-800">Editar Cliente</h2>

    <form [formGroup]="form" (ngSubmit)="submit()" *ngIf="!loading">
      <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Nombre *</label>
          <input 
            pInputText 
            formControlName="name" 
            class="w-full"
            [class.ng-invalid]="form.get('name')?.invalid && form.get('name')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('name')">
            {{ getFieldError('name') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Dirección *</label>
          <input 
            pInputText 
            formControlName="address" 
            class="w-full"
            [class.ng-invalid]="form.get('address')?.invalid && form.get('address')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('address')">
            {{ getFieldError('address') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Teléfono *</label>
          <input 
            pInputText 
            formControlName="phone" 
            class="w-full"
            [class.ng-invalid]="form.get('phone')?.invalid && form.get('phone')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('phone')">
            {{ getFieldError('phone') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Email *</label>
          <input 
            pInputText 
            type="email"
            formControlName="email" 
            class="w-full"
            [class.ng-invalid]="form.get('email')?.invalid && form.get('email')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('email')">
            {{ getFieldError('email') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Contraseña *</label>
          <input 
            pInputText 
            type="password" 
            formControlName="password" 
            class="w-full"
            [class.ng-invalid]="form.get('password')?.invalid && form.get('password')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('password')">
            {{ getFieldError('password') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Estado *</label>
          <p-select
            formControlName="status"
            [options]="statusOptions"
            placeholder="Seleccionar estado"
            class="w-full"
          ></p-select>
        </div>
      </div>

      <div class="flex justify-center mt-8">
        <div class="bg-gray-50 rounded-lg p-4 flex gap-4 items-center">
          <button
            pButton
            type="submit"
            class="p-button bg-blue-500 hover:bg-blue-600 text-white"
            label="Actualizar"
            icon="pi pi-save"
            [loading]="loading"
            [disabled]="loading"
          ></button>
          <button
            pButton
            type="button"
            class="p-button p-button-secondary"
            label="Cancelar"
            icon="pi pi-times"
            (click)="cancelar()"
            [disabled]="loading"
          ></button>
        </div>
      </div>
    </form>

    <div class="flex justify-center" *ngIf="loading">
      <i class="pi pi-spin pi-spinner" style="font-size: 2rem"></i>
    </div>
  </div>
</div>

<p-toast></p-toast>