Buen dia..
No estoy fijando una solucion ideal, solo a manera de conocimiento y que se pueda obtener una realimentacion .
Pasada la etapa de reconocimiento del webinar : HIX + HTML + DBF
Llegamos a la parte más compleja como lo es la creacion de la vista o front-End !!! hacerlo a pulso al estilo fivewin, usando templates, usando IA + template, etc..
Voy a insertar 4 archivos en bloques (cada bloque esta separado segĂşn tipo de archivo fĂsico)
Los bloques (ITEM 2, 3 Y 4), fueron generados con COPILOT
1. ejemplo tomado de : HIX & AntiGravity apps gallery - foro de fivewin
// agenda.prg
function Main()
local cAction := Upost( "action" )
local cId := Upost( "id" )
local cFirstname := Upost( "firstname" )
local cLastname := Upost( "lastname" )
local cPhone := Upost( "phone" )
local cEmail := Upost( "email" )
// Search Parameter (Post)
local cSearch := Upost( "search" )
local cDbPath := "c:/hix/testdir/agenda.dbf"
local cTmpDir := "c:/hix/testdir"
local cMessage := ""
// Ensure Directory Exists
if ! IsDirectory( cTmpDir )
DirMake( cTmpDir )
endif
if ! File( cDbPath )
DbCreate( cDbPath, { ;
{ "ID", "C", 8, 0 }, ;
{ "FIRSTNAME", "C", 30, 0 }, ;
{ "LASTNAME", "C", 30, 0 }, ;
{ "PHONE", "C", 20, 0 }, ;
{ "EMAIL", "C", 50, 0 } ;
} )
endif
// Robust Opening Logic
if Select("contacts") > 0
select contacts
else
select 0
use (cDbPath) shared alias "contacts"
if NetErr()
UWrite( "Error opening database.<br>" )
return ""
endif
endif
set deleted on
do case
case cAction == "save"
if !Empty(cFirstname) .and. !Empty(cLastname)
select contacts
if Empty( cId )
append blank
replace id with GenerateID()
replace firstname with cFirstname
replace lastname with cLastname
replace phone with cPhone
replace email with cEmail
commit
unlock
cMessage := "Contact saved successfully."
else
// Update existing
go top
locate for id == cId
if found()
if Rlock()
replace firstname with cFirstname
replace lastname with cLastname
replace phone with cPhone
replace email with cEmail
commit
unlock
cMessage := "Contact updated successfully."
endif
else
// ID not found? Treat as new.
append blank
replace id with GenerateID()
replace firstname with cFirstname
replace lastname with cLastname
replace phone with cPhone
replace email with cEmail
commit
unlock
cMessage := "Contact saved successfully."
endif
endif
endif
cId := ""
cFirstname := ""
cLastname := ""
cPhone := ""
cEmail := ""
// Clear search on save
cSearch := ""
case cAction == "delete"
if ! Empty( cId )
select contacts
go top
locate for id == cId
if found()
if Rlock()
delete
unlock
commit
cMessage := "Contact deleted successfully."
else
cMessage := "Error: Could not lock record (" + AllTrim(cId) + ") for deletion."
endif
else
cMessage := "Error: Contact ID (" + AllTrim(cId) + ") not found."
endif
else
cMessage := "Error: No ID provided for deletion."
endif
cId := ""
cSearch := "" // Clear search context on delete
case cAction == "edit"
select contacts
go top
locate for id == cId
if found()
cFirstname := contacts->firstname
cLastname := contacts->lastname
cPhone := contacts->phone
cEmail := contacts->email
endif
case cAction == "cancel"
cId := ""
cFirstname := ""
cLastname := ""
cPhone := ""
cEmail := ""
cSearch := ""
case cAction == "search"
// Handled by default read of cSearch parameter logic
// No specific logic needed here, just don't clear it.
endcase
RenderView( cId, cFirstname, cLastname, cPhone, cEmail, cMessage, cSearch )
select contacts
use
return ""
//****************************
function RenderView( cId, cFirst, cLast, cPhone, cEmail, cMessage, cSearch )
local cHtml := MemoRead( "c:/hix/agenda.view" )
local cRows := ""
local cFormAction := "agenda.prg"
local cMsgHtml := ""
local lMatch := .T.
local cFullData := ""
if Empty( cHtml )
UWrite( "Error: Could not read agenda.view template." )
return ""
endif
select contacts
go top
do while ! Eof()
lMatch := .T.
// Search Filter Logic
if !Empty( cSearch )
cFullData := Upper(contacts->firstname + " " + contacts->lastname + " " + contacts->phone + " " + contacts->email)
if ! ( Upper(AllTrim(cSearch)) $ cFullData )
lMatch := .F.
endif
endif
if lMatch
cRows += [<tr>]
cRows += [<td>] + AllTrim( contacts->firstname ) + [ ] + AllTrim( contacts->lastname ) + [</td>]
cRows += [<td>] + AllTrim( contacts->phone ) + [</td>]
cRows += [<td class="actions-cell">]
// POST Form for Edit
cRows += [<form method="POST" action="] + cFormAction + [" style="display:inline;">]
cRows += [<input type="hidden" name="action" value="edit">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(contacts->id) + [">]
cRows += [<input type="hidden" name="search" value="] + cSearch + [">]
cRows += [<button type="submit" class="btn btn-sm btn-edit"><i class="fa-solid fa-pen"></i></button>]
cRows += [</form> ]
// POST Form for Delete
cRows += [<form method="POST" action="] + cFormAction + [" style="display:inline;" onsubmit="return confirm('Delete contact?')">]
cRows += [<input type="hidden" name="action" value="delete">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(contacts->id) + [">]
cRows += [<input type="hidden" name="search" value="] + cSearch + [">]
cRows += [<button type="submit" class="btn btn-sm btn-danger"><i class="fa-solid fa-trash"></i></button>]
cRows += [</form>]
cRows += [</td></tr>]
endif
skip
enddo
if Empty( cRows )
if !Empty( cSearch )
cRows := [<tr><td colspan="3" class="empty-state">No contacts found matching "] + cSearch + [".</td></tr>]
else
cRows := [<tr><td colspan="3" class="empty-state">No contacts found. Add one above!</td></tr>]
endif
endif
// Process Message
if !Empty( cMessage )
// Determine type of alert
// If message starts with "Error", use alert-danger (red)
if Left( cMessage, 5 ) == "Error"
cMsgHtml := [<div class="alert alert-danger" style="color: #f87171; background-color: rgba(248, 113, 113, 0.2); border: 1px solid rgba(248, 113, 113, 0.3);">] + cMessage + [</div>]
else
cMsgHtml := [<div class="alert alert-success">] + cMessage + [</div>]
endif
endif
cHtml := StrTran( cHtml, "{{ACTION}}", cFormAction )
cHtml := StrTran( cHtml, "{{VAL_ID}}", iif( Empty(cId), "", cId ) )
cHtml := StrTran( cHtml, "{{VAL_FIRSTNAME}}", iif( Empty(cFirst), "", cFirst ) )
cHtml := StrTran( cHtml, "{{VAL_LASTNAME}}", iif( Empty(cLast), "", cLast ) )
cHtml := StrTran( cHtml, "{{VAL_PHONE}}", iif( Empty(cPhone), "", cPhone ) )
cHtml := StrTran( cHtml, "{{VAL_EMAIL}}", iif( Empty(cEmail), "", cEmail ) )
cHtml := StrTran( cHtml, "{{CONTACTS_ROWS}}", cRows )
cHtml := StrTran( cHtml, "{{MESSAGE}}", cMsgHtml )
cHtml := StrTran( cHtml, "{{VAL_SEARCH}}", iif( Empty(cSearch), "", cSearch ) )
// Add title dynamic
if !Empty(cId)
cHtml := StrTran( cHtml, "{{FORM_TITLE}}", "Edit Contact" )
cHtml := StrTran( cHtml, "{{CANCEL_BUTTON}}", [<a href="] + cFormAction + [?action=cancel" class="btn" style="background-color: #475569; color: white; margin-top: 0.5rem; justify-content:center;">Cancel Update</a>] )
else
cHtml := StrTran( cHtml, "{{FORM_TITLE}}", "Add Contact" )
cHtml := StrTran( cHtml, "{{CANCEL_BUTTON}}", "" )
endif
UWrite( cHtml )
return ""
function GenerateID()
local cChars := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
local cId := ""
local i
for i := 1 to 8
cId += SubStr( cChars, Int( hb_Random() * Len(cChars) ) + 1, 1 )
next
return cId
//**************************************************************************************************
// "c:/hix/agenda.view"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HIX Agenda</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #6366f1;
--primary-hover: #4f46e5;
--danger-color: #ef4444;
--bg-color: #0f172a;
--card-bg: #1e293b;
--text-color: #f8fafc;
--text-muted: #94a3b8;
--input-bg: #334155;
--border-color: #475569;
}
body {
font-family: 'Outfit', sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 2rem;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
width: 100%;
max-width: 900px;
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
}
@media (min-width: 768px) {
.container {
grid-template-columns: 350px 1fr;
}
}
h1 {
text-align: center;
margin-bottom: 2rem;
font-weight: 600;
background: linear-gradient(to right, #818cf8, #c4b5fd);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
width: 100%;
}
.card {
background-color: var(--card-bg);
padding: 1.5rem;
border-radius: 1rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
border: 1px solid rgba(255, 255, 255, 0.05);
height: fit-content;
}
.card-header {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 1.5rem;
color: var(--text-color);
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.5rem;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-size: 0.875rem;
color: var(--text-muted);
}
input {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
background-color: var(--input-bg);
color: white;
font-family: inherit;
box-sizing: border-box;
transition: all 0.2s;
}
input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
}
.btn {
width: 100%;
padding: 0.75rem;
border: none;
border-radius: 0.5rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
text-decoration: none;
font-size: 0.9rem;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-hover);
}
.btn-sm {
padding: 0.4rem 0.8rem;
font-size: 0.8rem;
width: auto;
}
.btn-danger {
background-color: transparent;
color: var(--danger-color);
border: 1px solid var(--danger-color);
}
.btn-danger:hover {
background-color: var(--danger-color);
color: white;
}
.btn-edit {
background-color: transparent;
color: var(--primary-color);
border: 1px solid var(--primary-color);
}
.btn-edit:hover {
background-color: var(--primary-color);
color: white;
}
.btn-search {
width: auto;
align-self: flex-end;
margin-bottom: 2px;
}
table {
width: 100%;
border-collapse: collapse;
color: var(--text-color);
}
th, td {
text-align: left;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
th {
color: var(--text-muted);
font-weight: 600;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
tr:last-child td {
border-bottom: none;
}
.empty-state {
text-align: center;
padding: 2rem;
color: var(--text-muted);
}
.actions-cell {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
.alert {
padding: 1rem;
margin-bottom: 1rem;
border-radius: 0.5rem;
font-weight: 500;
text-align: center;
}
.alert-success {
background-color: rgba(16, 185, 129, 0.2);
color: #34d399;
border: 1px solid rgba(16, 185, 129, 0.3);
}
.search-bar {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
</style>
</head>
<body>
<h1><i class="fa-solid fa-address-book"></i> HIX Agenda</h1>
<div class="container">
<!-- Form Section -->
<div class="card">
<div class="card-header">
{{FORM_TITLE}}
</div>
{{MESSAGE}}
<form method="POST" action="{{ACTION}}">
<input type="hidden" name="action" value="save">
<input type="hidden" name="id" value="{{VAL_ID}}">
<div class="form-group">
<label>First Name</label>
<input type="text" name="firstname" value="{{VAL_FIRSTNAME}}" required placeholder="John">
</div>
<div class="form-group">
<label>Last Name</label>
<input type="text" name="lastname" value="{{VAL_LASTNAME}}" required placeholder="Doe">
</div>
<div class="form-group">
<label>Phone</label>
<input type="tel" name="phone" value="{{VAL_PHONE}}" placeholder="+1 234 567 890">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" name="email" value="{{VAL_EMAIL}}" placeholder="john@example.com">
</div>
<button type="submit" class="btn btn-primary">
<i class="fa-solid fa-floppy-disk"></i> Save Contact
</button>
{{CANCEL_BUTTON}}
</form>
</div>
<!-- List Section -->
<div class="card">
<div class="card-header">
Contacts
</div>
<form method="POST" action="{{ACTION}}" class="search-bar">
<input type="hidden" name="action" value="search">
<input type="text" name="search" value="{{VAL_SEARCH}}" placeholder="Search contacts..." style="flex-grow:1;">
<button type="submit" class="btn btn-primary btn-search"><i class="fa-solid fa-magnifying-glass"></i></button>
<a href="{{ACTION}}" class="btn btn-search" style="background: #334155;"><i class="fa-solid fa-xmark"></i></a>
</form>
<table>
<thead>
<tr>
<th>Name</th>
<th>Phone / Email</th>
<th style="text-align: right;">Actions</th>
</tr>
</thead>
<tbody>
{{CONTACTS_ROWS}}
</tbody>
</table>
</div>
</div>
</body>
</html>
*************************************************************************************
*************************************************************************************
2. BACKEND EN HIX (USADO EN LOS BLOQUE O ITEMS 3 Y 4) )
#include "hbjson.ch"
function Main()
local cMethod := GetEnv("REQUEST_METHOD")
local cPath := GetEnv("PATH_INFO")
do case
case cMethod == "GET"
return GetRecords()
case cMethod == "POST"
return CreateRecord()
case cMethod == "PUT"
return UpdateRecord()
case cMethod == "DELETE"
return DeleteRecord()
otherwise
? "{ \"error\": \"Método no soportado\" }"
endcase
return nil
//************************
function GetRecords()
local aData := {}
USE people NEW SHARED
DO WHILE !EOF()
AAdd(aData, { ;
"ID" => AllTrim(people->ID), ;
"FIRSTNAME" => AllTrim(people->FIRSTNAME), ;
"LASTNAME" => AllTrim(people->LASTNAME), ;
"PHONE" => AllTrim(people->PHONE), ;
"EMAIL" => AllTrim(people->EMAIL) ;
})
SKIP
ENDDO
USE
? hb_jsonEncode(aData)
return nil
//********************************
function CreateRecord()
local hData := hb_jsonDecode( ReadStdIn() )
USE people NEW EXCLUSIVE
APPEND BLANK
REPLACE people->ID WITH hData["ID"]
REPLACE people->FIRSTNAME WITH hData["FIRSTNAME"]
REPLACE people->LASTNAME WITH hData["LASTNAME"]
REPLACE people->PHONE WITH hData["PHONE"]
REPLACE people->EMAIL WITH hData["EMAIL"]
USE
? "{ \"status\": \"created\" }"
return nil
//***************************
function UpdateRecord()
local hData := hb_jsonDecode( ReadStdIn() )
USE people NEW EXCLUSIVE
LOCATE FOR people->ID == hData["ID"]
if Found()
REPLACE people->FIRSTNAME WITH hData["FIRSTNAME"]
REPLACE people->LASTNAME WITH hData["LASTNAME"]
REPLACE people->PHONE WITH hData["PHONE"]
REPLACE people->EMAIL WITH hData["EMAIL"]
? "{ \"status\": \"updated\" }"
else
? "{ \"error\": \"ID no encontrado\" }"
endif
USE
return nil
//*********************************
function DeleteRecord()
local hData := hb_jsonDecode( ReadStdIn() )
USE people NEW EXCLUSIVE
LOCATE FOR people->ID == hData["ID"]
if Found()
DELETE
? "{ \"status\": \"deleted\" }"
else
? "{ \"error\": \"ID no encontrado\" }"
endif
USE
return nil
*************************************************************************************
*************************************************************************************
3. front-End : html + js + css .. puro
/* .html */
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>GestiĂłn de Personas</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>GestiĂłn de Personas</h1>
</header>
<main>
<section class="table-section">
<button id="btnAdd">âž• Nuevo Registro</button>
<table id="peopleTable">
<thead>
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Apellido</th>
<th>Teléfono</th>
<th>Email</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<!-- Filas dinámicas -->
</tbody>
</table>
</section>
<!-- Modal -->
<div id="modal" class="modal">
<div class="modal-content">
<span id="closeModal" class="close">×</span>
<h2 id="modalTitle">Nuevo Registro</h2>
<form id="personForm">
<label for="ID">ID</label>
<input type="text" id="ID" required>
<label for="FIRSTNAME">Nombre</label>
<input type="text" id="FIRSTNAME" required>
<label for="LASTNAME">Apellido</label>
<input type="text" id="LASTNAME" required>
<label for="PHONE">Teléfono</label>
<input type="text" id="PHONE">
<label for="EMAIL">Email</label>
<input type="email" id="EMAIL">
<button type="submit" id="saveBtn">Guardar</button>
</form>
</div>
</div>
</main>
<footer>
<p>© 2026 Empresa XYZ - Gestión de Personas</p>
</footer>
<!-- Capa JS separada -->
<script src="app.js"></script>
</body>
</html>
////////////////////////////
// .css
body {
font-family: 'Segoe UI', Tahoma, sans-serif;
margin: 0;
background: #f4f6f9;
color: #333;
}
header {
background: #004080;
color: white;
padding: 1rem;
text-align: center;
}
.table-section {
padding: 2rem;
}
button {
background: #004080;
color: white;
border: none;
padding: 0.5rem 1rem;
margin-bottom: 1rem;
cursor: pointer;
border-radius: 4px;
}
button:hover {
background: #0066cc;
}
table {
width: 100%;
border-collapse: collapse;
background: white;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
th, td {
padding: 0.75rem;
border-bottom: 1px solid #ddd;
}
th {
background: #e6e6e6;
}
footer {
background: #004080;
color: white;
text-align: center;
padding: 1rem;
}
/* Modal */
.modal {
display: none;
position: fixed;
z-index: 10;
left: 0; top: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
}
.modal-content {
background: white;
margin: 10% auto;
padding: 2rem;
width: 400px;
border-radius: 8px;
}
.close {
float: right;
font-size: 1.5rem;
cursor: pointer;
}
//*******************************
// .js logica de vista
const apiUrl = '/api/people';
const tableBody = document.querySelector('#peopleTable tbody');
const modal = document.getElementById('modal');
const closeModal = document.getElementById('closeModal');
const btnAdd = document.getElementById('btnAdd');
const form = document.getElementById('personForm');
let editing = false;
// Cargar registros
async function loadPeople() {
const res = await fetch(apiUrl);
const data = await res.json();
tableBody.innerHTML = '';
data.forEach(person => {
const row = document.createElement('tr');
row.innerHTML = `
<td>${person.ID}</td>
<td>${person.FIRSTNAME}</td>
<td>${person.LASTNAME}</td>
<td>${person.PHONE}</td>
<td>${person.EMAIL}</td>
<td>
<button class="editBtn" data-id="${person.ID}">✏️</button>
<button class="deleteBtn" data-id="${person.ID}">🗑️</button>
</td>
`;
tableBody.appendChild(row);
});
// Asignar eventos dinámicos
document.querySelectorAll('.editBtn').forEach(btn =>
btn.addEventListener('click', () => editPerson(btn.dataset.id))
);
document.querySelectorAll('.deleteBtn').forEach(btn =>
btn.addEventListener('click', () => deletePerson(btn.dataset.id))
);
}
// Crear o actualizar
form.addEventListener('submit', async e => {
e.preventDefault();
const person = {
ID: document.getElementById('ID').value,
FIRSTNAME: document.getElementById('FIRSTNAME').value,
LASTNAME: document.getElementById('LASTNAME').value,
PHONE: document.getElementById('PHONE').value,
EMAIL: document.getElementById('EMAIL').value
};
const method = editing ? 'PUT' : 'POST';
await fetch(apiUrl, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(person)
});
modal.style.display = 'none';
loadPeople();
});
// Editar
async function editPerson(id) {
const res = await fetch(apiUrl);
const data = await res.json();
const person = data.find(p => p.ID === id);
if (person) {
document.getElementById('ID').value = person.ID;
document.getElementById('FIRSTNAME').value = person.FIRSTNAME;
document.getElementById('LASTNAME').value = person.LASTNAME;
document.getElementById('PHONE').value = person.PHONE;
document.getElementById('EMAIL').value = person.EMAIL;
editing = true;
modal.style.display = 'block';
}
}
// Eliminar
async function deletePerson(id) {
await fetch(apiUrl, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ID: id })
});
loadPeople();
}
// Modal control
btnAdd.onclick = () => {
form.reset();
editing = false;
modal.style.display = 'block';
};
closeModal.onclick = () => modal.style.display = 'none';
window.onclick = e => { if (e.target === modal) modal.style.display = 'none'; };
// Inicializar
loadPeople();
*************************************************************************************
*************************************************************************************
4. front-End : frameWork angular
// .html
<p-table [value]="people" [(selection)]="selectedPerson" dataKey="ID">
<ng-template pTemplate="header">
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Apellido</th>
<th>Teléfono</th>
<th>Email</th>
<th>Acciones</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-person>
<tr>
<td>{{person.ID}}</td>
<td>{{person.FIRSTNAME}}</td>
<td>{{person.LASTNAME}}</td>
<td>{{person.PHONE}}</td>
<td>{{person.EMAIL}}</td>
<td>
<button pButton type="button" icon="pi pi-pencil" (click)="editPerson(person)"></button>
<button pButton type="button" icon="pi pi-trash" (click)="delete(person)"></button>
</td>
</tr>
</ng-template>
</p-table>
<button pButton type="button" label="Nuevo" icon="pi pi-plus" (click)="addPerson()"></button>
<p-dialog header="Persona" [(visible)]="displayDialog" [modal]="true">
<div class="p-fluid">
<div class="p-field">
<label for="firstname">Nombre</label>
<input id="firstname" type="text" pInputText [(ngModel)]="selectedPerson.FIRSTNAME"/>
</div>
<div class="p-field">
<label for="lastname">Apellido</label>
<input id="lastname" type="text" pInputText [(ngModel)]="selectedPerson.LASTNAME"/>
</div>
<div class="p-field">
<label for="phone">Teléfono</label>
<input id="phone" type="text" pInputText [(ngModel)]="selectedPerson.PHONE"/>
</div>
<div class="p-field">
<label for="email">Email</label>
<input id="email" type="text" pInputText [(ngModel)]="selectedPerson.EMAIL"/>
</div>
</div>
<p-footer>
<button pButton type="button" label="Guardar" (click)="save()"></button>
</p-footer>
</p-dialog>
//*******************************************************************
// . logica en vista .js o ts
import { Component, OnInit } from '@angular/core';
import { PeopleService, Person } from './people.service';
@Component({
selector: 'app-people',
templateUrl: './people.component.html'
})
export class PeopleComponent implements OnInit {
people: Person[] = [];
selectedPerson: Person | null = null;
displayDialog: boolean = false;
newPerson: boolean = false;
constructor(private peopleService: PeopleService) {}
ngOnInit() {
this.loadPeople();
}
loadPeople() {
this.peopleService.getAll().subscribe(data => this.people = data);
}
addPerson() {
this.newPerson = true;
this.selectedPerson = { ID:'', FIRSTNAME:'', LASTNAME:'', PHONE:'', EMAIL:'' };
this.displayDialog = true;
}
editPerson(person: Person) {
this.newPerson = false;
this.selectedPerson = { ...person };
this.displayDialog = true;
}
save() {
if (this.newPerson) {
this.peopleService.create(this.selectedPerson!).subscribe(() => this.loadPeople());
} else {
this.peopleService.update(this.selectedPerson!).subscribe(() => this.loadPeople());
}
this.displayDialog = false;
}
delete(person: Person) {
this.peopleService.delete(person.ID).subscribe(() => this.loadPeople());
}
}
//****************************************
// .service
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface Person {
ID: string;
FIRSTNAME: string;
LASTNAME: string;
PHONE: string;
EMAIL: string;
}
@Injectable({
providedIn: 'root'
})
export class PeopleService {
private apiUrl = '/api/people'; // tu endpoint Hix
constructor(private http: HttpClient) {}
getAll(): Observable<Person[]> {
return this.http.get<Person[]>(this.apiUrl);
}
create(person: Person): Observable<any> {
return this.http.post(this.apiUrl, person);
}
update(person: Person): Observable<any> {
return this.http.put(this.apiUrl, person);
}
delete(id: string): Observable<any> {
return this.http.request('delete', this.apiUrl, { body: { ID: id } });
}
}
//*****************************************************************