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 } from '@angular/common/http';
import { Observable, BehaviorSubject, tap, catchError, throwError } from 'rxjs';
import { map } from 'rxjs/operators';
import { ClientI, ClientResponseI } from '../models/client';

// Interfaz para la respuesta paginada de Django
interface PaginatedResponse<T> {
  count: number;
  next: string | null;
  previous: string | null;
  results: T[];
}

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

  constructor(private http: HttpClient) {}

  getAllClients(): Observable<ClientResponseI[]> {
    return this.http.get<PaginatedResponse<ClientResponseI>>(`${this.baseUrl}/`)
      .pipe(
        map(response => response.results), // Extraer solo el array de results
        tap(clients => {
          console.log('Fetched clients:', clients);
          this.clientsSubject.next(clients);
        }),
        catchError(error => {
          console.error('Error fetching clients:', error);
          return throwError(() => error);
        })
      );
  }

  getClientById(id: number): Observable<ClientResponseI> {
    return this.http.get<ClientResponseI>(`${this.baseUrl}/${id}/`)
      .pipe(
        catchError(error => {
          console.error('Error fetching client:', error);
          return throwError(() => error);
        })
      );
  }

  createClient(client: ClientI): Observable<ClientResponseI> {
    return this.http.post<ClientResponseI>(`${this.baseUrl}/`, client)
      .pipe(
        tap(response => {
          console.log('Client created:', response);
          this.refreshClients();
        }),
        catchError(error => {
          console.error('Error creating client:', error);
          return throwError(() => error);
        })
      );
  }

  updateClient(id: number, client: Partial<ClientI>): Observable<ClientResponseI> {
    return this.http.put<ClientResponseI>(`${this.baseUrl}/${id}/`, client)
      .pipe(
        tap(response => {
          console.log('Client updated:', response);
          this.refreshClients();
        }),
        catchError(error => {
          console.error('Error updating client:', error);
          return throwError(() => error);
        })
      );
  }

  partialUpdateClient(id: number, client: Partial<ClientI>): Observable<ClientResponseI> {
    return this.http.patch<ClientResponseI>(`${this.baseUrl}/${id}/`, client)
      .pipe(
        tap(response => {
          console.log('Client partially updated:', response);
          this.refreshClients();
        }),
        catchError(error => {
          console.error('Error partially updating client:', error);
          return throwError(() => error);
        })
      );
  }

  deleteClient(id: number): Observable<void> {
    return this.http.delete<void>(`${this.baseUrl}/${id}/`)
      .pipe(
        tap(() => {
          console.log('Client deleted:', id);
          this.refreshClients();
        }),
        catchError(error => {
          console.error('Error deleting client:', error);
          return throwError(() => error);
        })
      );
  }

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

  refreshClients(): void {
    this.getAllClients().subscribe({
      next: (clients) => {
        this.clientsSubject.next(clients);
      },
      error: (error) => {
        console.error('Error refreshing clients:', error);
      }
    });
  }
}

2. Enlazamos con el componente Client

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

src/app/components/client/getall.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { TableModule } from 'primeng/table';
import { Button } from 'primeng/button';
import { ConfirmDialog } from 'primeng/confirmdialog';
import { Toast } from 'primeng/toast';
import { Tooltip } from 'primeng/tooltip';
import { ConfirmationService, MessageService } from 'primeng/api';
import { Subscription } from 'rxjs';
import { ClientService } from '../../../services/client.service';
import { ClientResponseI } from '../../../models/client';

@Component({
  selector: 'app-client-getall',
  standalone: true,
  imports: [
    CommonModule,
    RouterModule,
    TableModule,
    Button,
    ConfirmDialog,
    Toast,
    Tooltip
  ],
  providers: [ConfirmationService, MessageService],
  templateUrl: './getall.html',
  styleUrl: './getall.css'
})
export class Getall implements OnInit, OnDestroy {
  clients: ClientResponseI[] = [];
  loading = false;
  private subscription = new Subscription();

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

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

    // Subscribe to client updates
    this.subscription.add(
      this.clientService.clients$.subscribe(clients => {
        this.clients = clients;
      })
    );
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }

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

  confirmDelete(client: ClientResponseI): void {
    this.confirmationService.confirm({
      message: `¿Está seguro de que desea eliminar a ${client.name}?`,
      header: 'Confirmar Eliminación',
      icon: 'pi pi-exclamation-triangle',
      acceptLabel: 'Sí, eliminar',
      rejectLabel: 'Cancelar',
      acceptButtonStyleClass: 'p-button-danger',
      accept: () => {
        this.deleteClient(client.id!);
      }
    });
  }

  deleteClient(id: number): void {
    this.subscription.add(
      this.clientService.deleteClient(id).subscribe({
        next: () => {
          this.messageService.add({
            severity: 'success',
            summary: 'Éxito',
            detail: 'Cliente eliminado correctamente'
          });
        },
        error: (error) => {
          console.error('Error deleting client:', error);
          this.messageService.add({
            severity: 'error',
            summary: 'Error',
            detail: 'No se pudo 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>
        <p-button
            label="Nuevo Cliente"
            icon="pi pi-plus"
            [routerLink]="['/clients/new']"
            styleClass="p-button-success"
        ></p-button>
    </div>

    <p-table 
        [value]="clients" 
        [loading]="loading"
        [paginator]="true"
        [rows]="10"
        [showCurrentPageReport]="true"
        currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} registros"
        [rowsPerPageOptions]="[5, 10, 25, 50]"
        dataKey="id"
        styleClass="p-datatable-striped"
        [tableStyle]="{'min-width': '50rem'}"
    >
        <ng-template pTemplate="header">
            <tr>
                <th pSortableColumn="id">
                    ID <p-sortIcon field="id"></p-sortIcon>
                </th>
                <th pSortableColumn="name">
                    Nombre <p-sortIcon field="name"></p-sortIcon>
                </th>
                <th pSortableColumn="address">
                    Dirección <p-sortIcon field="address"></p-sortIcon>
                </th>
                <th pSortableColumn="phone">
                    Teléfono <p-sortIcon field="phone"></p-sortIcon>
                </th>
                <th pSortableColumn="email">
                    Email <p-sortIcon field="email"></p-sortIcon>
                </th>
                <th class="text-center">Acciones</th>
            </tr>
        </ng-template>

        <ng-template pTemplate="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 class="text-center">
                    <div class="flex justify-center gap-2">
                        <p-button
                            icon="pi pi-pencil"
                            [routerLink]="['/clients/edit', client.id]"
                            styleClass="p-button-rounded p-button-text p-button-warning"
                            pTooltip="Editar"
                            tooltipPosition="top"
                        ></p-button>
                        <p-button
                            icon="pi pi-trash"
                            (onClick)="confirmDelete(client)"
                            styleClass="p-button-rounded p-button-text p-button-danger"
                            pTooltip="Eliminar"
                            tooltipPosition="top"
                        ></p-button>
                    </div>
                </td>
            </tr>
        </ng-template>

        <ng-template pTemplate="emptymessage">
            <tr>
                <td colspan="6" class="text-center p-4">
                    <div class="flex flex-col items-center gap-3">
                        <i class="pi pi-info-circle text-4xl text-gray-400"></i>
                        <span class="text-gray-500">No se encontraron clientes</span>
                        <p-button
                            label="Crear Primer Cliente"
                            icon="pi pi-plus"
                            [routerLink]="['/clients/new']"
                            styleClass="p-button-sm p-button-outlined"
                        ></p-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';

// 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 SaleUpdate } from './components/sale/update/update';
import { Delete as SaleDelete } from './components/sale/delete/delete';

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

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 { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeuix/themes/aura';
import { routes } from './app.routes';

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

Al ejecutar en el terminal ng serve

Nota: 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)

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)

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>