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/business/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/business/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/business/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 Client {
  private baseUrl = 'http://127.0.0.1:8000/client/clients';
  private clientsSubject = new BehaviorSubject<ClientResponseI[]>([]);
  public clients$ = this.clientsSubject.asObservable();

  constructor(private http: HttpClient) {}

  private normalizeClientsResponse(
    response: PaginatedResponse<ClientResponseI> | ClientResponseI[]
  ): ClientResponseI[] {
    if (Array.isArray(response)) {
      return response;
    }
    return response.results ?? [];
  }

  getAllClients(): Observable<ClientResponseI[]> {
    return this.http
      .get<PaginatedResponse<ClientResponseI> | ClientResponseI[]>(`${this.baseUrl}/`)
      .pipe(
        map((response) => this.normalizeClientsResponse(response)),
        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 { Observable } from 'rxjs';
import { Client } from '../../../services/business/client';
import { ClientResponseI } from '../../../models/business/client';

@Component({
  selector: 'app-getall',
  imports: [
    CommonModule,
    RouterModule,
    TableModule,
    Button,
    ConfirmDialog,
    Toast,
    Tooltip
  ],
  providers: [ConfirmationService, MessageService],
  templateUrl: './getall.html',
  styleUrl: './getall.css',
})
export class Getall implements OnInit, OnDestroy{

  clients$: Observable<ClientResponseI[]>;
  loading = false;
  private subscription = new Subscription();

  constructor(
    private clientService: Client,
    private confirmationService: ConfirmationService,
    private messageService: MessageService
  ) {
    this.clients$ = this.clientService.clients$;
  }

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

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

  loadClients(): void {
    this.loading = true;
    this.subscription.add(
      this.clientService.getAllClients().subscribe({
        next: () => {
          // console.log('Clients loaded:', 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$ | async) ?? []" 
        [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]"
                            [state]="{ client: client }"
                            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 } from '@angular/core';
import { provideRouter } from '@angular/router';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeuix/themes/aura';

import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
   providers: [
    provideBrowserGlobalErrorListeners(),
    provideRouter(routes),
        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 { Client } from '../../../services/business/client';

@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: Client,
    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, OnDestroy, OnInit, signal } 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 { finalize, Subscription } from 'rxjs';
import { Client } from '../../../services/business/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, OnDestroy {
  form: FormGroup;
  loading = signal(false);
  loadingClient = signal(false);
  private clientId: number | null = null;
  private subscription = new Subscription();

  statusOptions = [
    { label: 'Activo', value: 'ACTIVE' },
    { label: 'Inactivo', value: 'INACTIVE' }
  ];

  constructor(
    private fb: FormBuilder,
    private route: ActivatedRoute,
    private router: Router,
    private clientService: Client,
    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.minLength(6)]],
      status: ['ACTIVE', Validators.required]
    });
  }

  ngOnInit(): void {
    const idParam = this.route.snapshot.paramMap.get('id');
    const parsedId = Number(idParam);

    if (!idParam || Number.isNaN(parsedId)) {
      this.messageService.add({
        severity: 'error',
        summary: 'Error',
        detail: 'ID de cliente no válido'
      });
      this.router.navigate(['/clients']);
      return;
    }

    this.clientId = parsedId;
    this.loadClient(parsedId);
  }

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

  private loadClient(id: number): void {
    this.loadingClient.set(true);
    this.subscription.add(
      this.clientService.getClientById(id).pipe(
        finalize(() => {
          this.loadingClient.set(false);
        })
      ).subscribe({
        next: (client: any) => {
          this.form.patchValue({
            name: client.name ?? '',
            address: client.address ?? '',
            phone: client.phone ?? '',
            email: client.email ?? '',
            password: '',
            status: client.status ?? 'ACTIVE'
          });

          this.form.get('password')?.setValue('');
        },
        error: (error) => {
          console.error('Error loading client:', error);
          this.messageService.add({
            severity: 'error',
            summary: 'Error',
            detail: 'No se pudo cargar el cliente'
          });
          this.router.navigate(['/clients']);
        }
      })
    );
  }

  submit(): void {
    if (!this.clientId) {
      this.messageService.add({
        severity: 'error',
        summary: 'Error',
        detail: 'No se encontró el cliente a actualizar'
      });
      return;
    }

    if (this.form.valid) {
      this.loading.set(true);
      const clientData = { ...this.form.value };

      if (!clientData.password) {
        delete clientData.password;
      }

      this.subscription.add(
        this.clientService.partialUpdateClient(this.clientId, clientData).subscribe({
          next: () => {
            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.set(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">Actualizar Cliente</h2>

        <div *ngIf="loadingClient()" class="text-center py-8 text-gray-600">
            Cargando datos del cliente...
        </div>

        <form *ngIf="!loadingClient()" [formGroup]="form" (ngSubmit)="submit()" autocomplete="off">
            <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 (opcional)</label>
                    <input
                        pInputText
                        type="password"
                        formControlName="password"
                        class="w-full"
                        autocomplete="new-password"
                        placeholder="Dejar en blanco para no cambiar"
                        [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>
</div>

<p-toast></p-toast>