FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour HIX & AntiGravity apps gallery
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX & AntiGravity apps gallery
Posted: Sat Dec 20, 2025 11:54 AM

facturas.prg

function Main()
    

local cAction    := Upost( "action" )
local cId        := Upost( "id" )
local cClientId  := Upost( "client_id" )
local cDate      := Upost( "date" )
local cStatus    := Upost( "status" )
local cLinesData := Upost( "lines_data" ) // Format: PROD_ID,QTY;PROD_ID,QTY

// Search Parameter (Post)
local cSearch    := Upost( "search" )

local cTmpDir    := "c:/hix/testdir"
local cDbFact    := cTmpDir + "/facturas_v2.dbf"
local cDbLines   := cTmpDir + "/facturas_lines.dbf"
local cDbCli     := cTmpDir + "/clientes_v2.dbf"
local cDbProd    := cTmpDir + "/productos.dbf"

local cMessage   := ""
local cTotal     := "0.00"

// Ensure Directory Exists
if ! IsDirectory( cTmpDir )
DirMake( cTmpDir )
endif

// 1. Initialize Databases
InitDatabases( cDbFact, cDbLines, cDbCli, cDbProd )

// 2. Open Databases Shared
if !OpenDatabases()
return "Error opening databases."
endif

// 3. Seed Data if Empty
SeedData()

set deleted on
set date format to "yyyy-mm-dd"

do case
case cAction == "save"
if !Empty(cClientId)
select facturas
if Empty( cId ) 
// New Invoice
append blank
if !NetErr()
cId := GenerateID()
replace id with cId
replace client_id with cClientId
replace date      with CToD( cDate )
replace status    with cStatus
                
// Save Lines & Calculate Total
cTotal := SaveLines( cId, cLinesData, .T. ) 
replace total with Val( cTotal )
                
commit
unlock
cMessage := "Invoice saved successfully."
else
cMessage := "Error: Could not create invoice (Lock failed)."
endif
else
// Update existing
go top
locate for AllTrim(id) == AllTrim(cId)
if found()
if Rlock()
replace client_id with cClientId
replace date      with CToD( cDate )
replace status    with cStatus // Note: Stock logic implies checking status change, simplfied here
                        
// Save Lines & Calculate Total
cTotal := SaveLines( cId, cLinesData, .F. )
replace total with Val( cTotal )

commit
unlock
cMessage := "Invoice updated successfully."
endif
endif
endif
            
// Handle Stock Deduction if Paid
if cStatus == "Paid"
UpdateStock( cId )
endif
endif
        
cId := "" 
cClientId := ""
cDate := ""
cTotal := ""
cStatus := ""
cSearch := ""
        
case cAction == "delete"
if ! Empty( cId )
// Delete Lines first
select lines
go top
do while ! Eof()
if lines->invoice_id == cId
if Rlock()
delete
unlock
endif
endif
skip
enddo
            
// Delete Header
select facturas
go top
locate for AllTrim(id) == AllTrim(cId)
if found() .and. Rlock()
delete
unlock
commit
cMessage := "Invoice deleted successfully."
endif
endif
cId := ""
cSearch := "" 

case cAction == "edit"
select facturas
go top
locate for AllTrim(id) == AllTrim(cId)
if found()
cClientId := facturas->client_id
cDate     := DToC( facturas->date )
cTotal    := Str( facturas->total )
cStatus   := facturas->status
endif
        
case cAction == "cancel"
cId := "" 
cSearch := ""
    
endcase

RenderView( cId, cClientId, cDate, cTotal, cStatus, cMessage, cSearch )

CloseDatabases()

return ""

// --------------------------------------------------------------------------------
// Database Helpers
// --------------------------------------------------------------------------------

function InitDatabases( cDbFact, cDbLines, cDbCli, cDbProd )
    if ! File( cDbFact )
    DbCreate( cDbFact, { ;
        { "ID",        "C",  8, 0 }, ;
        { "CLIENT_ID", "C",  8, 0 }, ;
        { "DATE",      "D",  8, 0 }, ;
        { "TOTAL",     "N", 12, 2 }, ;
        { "STATUS",    "C", 10, 0 } ;
        } )
    endif
    

if ! File( cDbLines )
DbCreate( cDbLines, { ;
    { "INVOICE_ID","C",  8, 0 }, ;
    { "PROD_ID",   "C",  8, 0 }, ;
    { "QTY",       "N", 10, 2 }, ;
    { "PRICE",     "N", 10, 2 }, ;
    { "TOTAL",     "N", 12, 2 } ;
    } )
endif

if ! File( cDbCli )
DbCreate( cDbCli, { ;
    { "ID",   "C",  8, 0 }, ;
    { "NAME", "C", 50, 0 } ;
    } )
endif

if ! File( cDbProd )
DbCreate( cDbProd, { ;
    { "ID",    "C",  8, 0 }, ;
    { "NAME",  "C", 50, 0 }, ;
    { "PRICE", "N", 10, 2 }, ;
    { "STOCK", "N", 10, 2 } ;
    } )
endif
return nil

function OpenDatabases()
    if Select("facturas") == 0
    use ("c:/hix/testdir/facturas_v2.dbf") shared new alias "facturas"
    else
    select facturas
    endif

if Select("lines") == 0
use ("c:/hix/testdir/facturas_lines.dbf") shared new alias "lines"
else
select lines
endif

if Select("clients") == 0
use ("c:/hix/testdir/clientes.dbf") shared new alias "clients"
else
select clients
endif

if Select("products") == 0
use ("c:/hix/testdir/productos.dbf") shared new alias "products"
else
select products
endif
return .T.

function CloseDatabases()
    close all
return nil

function SeedData()
    select clients
    if RecCount() == 0
    append blank
    replace id with "C1", name with "Acme Corp"
    append blank
    replace id with "C2", name with "Globex Inc."
    append blank
    replace id with "C3", name with "Wayne Ent."
    commit
    endif
    

select products
if RecCount() == 0
append blank
replace id with "P1", name with "Widget A", price with 10.00, stock with 100
append blank
replace id with "P2", name with "Widget B", price with 25.50, stock with 50
append blank
replace id with "P3", name with "Premium X", price with 99.99, stock with 10
commit
endif
return nil

// --------------------------------------------------------------------------------
// Logic Helpers
// --------------------------------------------------------------------------------

function SaveLines( cInvoiceId, cData, lNew )
    local aItems := hb_ATokens( cData, ";" )
    local aParts
    local cItem
    local nGrandTotal := 0
    local nPrice := 0
    local nQty := 0
    local nRowTotal := 0
    

select lines

// If not new, delete old lines first
if !lNew
go top
do while !Eof()
if lines->invoice_id == cInvoiceId
if Rlock()
delete
unlock
endif
endif
skip
enddo
endif

// Add new lines
for each cItem in aItems
if !Empty(cItem)
aParts := hb_ATokens( cItem, "," ) // PROD_ID,QTY
if Len(aParts) >= 2
nQty := Val( aParts[2] )
            
// Lookup Price
select products
go top
locate for id == aParts[1]
if found()
nPrice := products->price
else
nPrice := 0
endif
            
nRowTotal := nQty * nPrice
nGrandTotal += nRowTotal
            
select lines
append blank
if !NetErr()
replace invoice_id with cInvoiceId
replace prod_id    with aParts[1]
replace qty        with nQty
replace price      with nPrice
replace total      with nRowTotal
unlock
endif
endif
endif
next

return AllTrim( Str( nGrandTotal ) )

function UpdateStock( cInvoiceId )
    // Simple implementation: Iterate lines and subtract stock
    // Real implementation should check if stock was already deducted
    select lines
    go top
    do while ! Eof()
    if lines->invoice_id == cInvoiceId
    select products
    go top
    locate for id == lines->prod_id
    if found() .and. Rlock()
    replace stock with stock - lines->qty
    unlock
    endif
    select lines
    endif
    skip
    enddo
return nil

function GenerateID()
    local cChars := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    local cNewId := ""
    local i
    for i := 1 to 8
    cNewId += SubStr( cChars, Int( hb_Random() * Len(cChars) ) + 1, 1 )
    next
return cNewId

// --------------------------------------------------------------------------------
// View Renderer
// --------------------------------------------------------------------------------

function RenderView( cId, cClientId, cDate, cTotal, cStatus, cMessage, cSearch )
    local cHtml := MemoRead( "c:/hix/facturas.view" )
    

// 1. Build Client Options
local cClientOpts := "<option value=''>Select Client</option>"
local cSelected := ""

// 2. Build Product Data for JS
local cProdScript := "var products = {"

// 3. Build Invoice Rows vars
local cRows := ""
local cCliName := ""
local lMatch := .T.
local cFullData := ""
local cStatusColor := ""

// MESSAGE VAR
local cMsgHtml := ""
local cLineScript := "var existingLines = [];"


// LOGIC START

select clients
go top
do while ! Eof()
cSelected := iif( AllTrim(clients->id) == AllTrim(cClientId), "selected", "" )
cClientOpts += [<option value="] + AllTrim(clients->id) + ["] + cSelected + [>] + AllTrim(clients->name) + [</option>]
skip
enddo

select products
go top
do while ! Eof()
cProdScript += ["] + AllTrim(products->id) + [": {name: "] + AllTrim(products->name) + [", price: ] + AllTrim(Str(products->price)) + [},]
skip
enddo
cProdScript += "};"

select facturas
go top
do while ! Eof()
lMatch := .T.
    
// Lookup Client Name
select clients
go top
locate for id == facturas->client_id
if found()
cCliName := AllTrim( clients->name )
else
cCliName := facturas->client_id
endif
select facturas
    
// Search Filter
if !Empty( cSearch )
cFullData := Upper(cCliName + " " + DToC(facturas->date) + " " + Str(facturas->total) + " " + facturas->status)
if ! ( Upper(AllTrim(cSearch)) $ cFullData )
lMatch := .F.
endif
endif

if lMatch
cRows += [<tr>]
cRows += [<td>] + AllTrim(facturas->id) + [</td>]
cRows += [<td>] + cCliName + [</td>]
cRows += [<td>] + DToC( facturas->date ) + [</td>]
cRows += [<td>] + AllTrim( Str( facturas->total ) ) + [</td>]
cRows += [<td>] + AllTrim( facturas->status ) + [</td>]
cRows += [<td class="actions-cell">]
cRows += [<form method="POST" action="facturas.prg" style="display:inline;">]
cRows += [<input type="hidden" name="action" value="edit">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(facturas->id) + [">]
cRows += [<button type="submit" class="btn btn-sm btn-edit"><i class="fa-solid fa-pen"></i></button>]
cRows += [</form> ]
cRows += [<form method="POST" action="facturas.prg" style="display:inline;" onsubmit="return confirm('Delete invoice?')">]
cRows += [<input type="hidden" name="action" value="delete">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(facturas->id) + [">]
cRows += [<button type="submit" class="btn btn-sm btn-danger"><i class="fa-solid fa-trash"></i></button>]
cRows += [</form>]
cRows += [<form method="POST" action="factura_pdf.prg" target="_blank" style="display:inline; margin-left: 4px;">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(facturas->id) + [">]
cRows += [<button type="submit" class="btn btn-sm btn-secondary" title="Download PDF"><i class="fa-solid fa-file-pdf"></i></button>]
cRows += [</form></td></tr>]
endif
skip
enddo

if !Empty( cId )
select lines
go top
do while ! Eof()
if lines->invoice_id == cId
cLineScript += [existingLines.push({prod_id: "] + AllTrim(lines->prod_id) + [", qty: ] + AllTrim(Str(lines->qty)) + [});]
endif
skip
enddo
endif


// Replacements
cHtml := StrTran( cHtml, "{{CLIENT_OPTIONS}}", cClientOpts )
cHtml := StrTran( cHtml, "{{PRODUCT_DATA}}", cProdScript )
cHtml := StrTran( cHtml, "{{EXISTING_LINES}}", cLineScript )
cHtml := StrTran( cHtml, "{{INVOICE_ROWS}}", cRows )

// Standard Fields
cHtml := StrTran( cHtml, "{{VAL_ID}}", iif( Empty(cId), "", cId ) )
cHtml := StrTran( cHtml, "{{VAL_DATE}}", iif( Empty(cDate), Date(), cDate ) )
cHtml := StrTran( cHtml, "{{VAL_TOTAL}}", iif( Empty(cTotal), "", AllTrim(cTotal) ) )
cHtml := StrTran( cHtml, "{{SEL_PAID}}", iif( cStatus == "Paid", "selected", "" ) )
cHtml := StrTran( cHtml, "{{SEL_PENDING}}", iif( cStatus == "Pending", "selected", "" ) )
cHtml := StrTran( cHtml, "{{SEL_CANCELLED}}", iif( cStatus == "Cancelled", "selected", "" ) )

// Message & Search
if !Empty( cMessage )
if Left( cMessage, 5 ) == "Error"
cMsgHtml := [<div class="alert alert-danger">] + cMessage + [</div>]
else
cMsgHtml := [<div class="alert alert-success">] + cMessage + [</div>]
endif
endif
cHtml := StrTran( cHtml, "{{MESSAGE}}", cMsgHtml )
cHtml := StrTran( cHtml, "{{VAL_SEARCH}}", iif( Empty(cSearch), "", cSearch ) )

if !Empty(cId)
cHtml := StrTran( cHtml, "{{FORM_TITLE}}", "Edit Invoice: " + cId )
cHtml := StrTran( cHtml, "{{CANCEL_BUTTON}}", [<a href="facturas.prg" class="btn btn-sm" style="background:#475569;margin-left:5px;">Cancel</a>] )
else
cHtml := StrTran( cHtml, "{{FORM_TITLE}}", "New Invoice" )
cHtml := StrTran( cHtml, "{{CANCEL_BUTTON}}", "" )
endif

UWrite( cHtml )

return ""

facturas.view

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Invoices Management</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <style>
        body { background-color: #0f172a; color: #e0f2fe; font-family: 'Inter', sans-serif; }
        .container-fluid { max-width: 1400px; padding: 20px; }
        .card { background-color: #1e293b; border: 1px solid #3b82f6; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.2); }
        .card-header { background-color: #1d4ed8; color: #e0f2fe; border-bottom: 1px solid #1e40af; border-radius: 12px 12px 0 0 !important; padding: 1rem 1.5rem; }
        

    /* Navbar Standardization */
    .navbar { background-color: #172554; border-bottom: 1px solid #1e3a8a; padding-top: 0.75rem; padding-bottom: 0.75rem; min-height: 70px; }
    .navbar-brand { font-weight: 600; color: #bae6fd !important; font-size: 1.25rem; }
    .nav-link { color: #bfdbfe; margin-right: 15px; font-weight: 500; }
    .nav-link:hover, .nav-link.active { color: #e0f2fe; font-weight: 600; }
    
    .form-label { color: #bfdbfe; font-weight: 500; font-size: 0.875rem; }
    .form-control, .form-select { background-color: #0f172a; border: 1px solid #3b82f6; color: #e0f2fe; border-radius: 8px; padding: 0.625rem 1rem; }
    .form-control:focus, .form-select:focus { background-color: #172554; border-color: #60a5fa; box-shadow: 0 0 0 2px rgba(96,165,250,0.3); color: #e0f2fe; }
    .btn-primary { background-color: #3b82f6; border: none; padding: 0.625rem 1.25rem; font-weight: 500; }
    .btn-primary:hover { background-color: #2563eb; }
    .btn-secondary { background-color: #1e40af; border: none; color: #e0f2fe; }
    .btn-secondary:hover { background-color: #1e3a8a; }
    .table { color: #0c4a6e; margin-bottom: 0; }
    .table thead th { background-color: #1e3a8a; border-bottom: none; color: #fff; font-weight: 600; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.05em; padding: 1rem; }
    .table tbody tr:nth-of-type(odd) { background-color: #e0f2fe; color: #0c4a6e; }
    .table tbody tr:nth-of-type(even) { background-color: #bae6fd; color: #0c4a6e; }
    .table td { border-bottom: 1px solid #7dd3fc; padding: 1rem; vertical-align: middle; border-top: none; }
    .table tr:last-child td { border-bottom: none; }
    .actions-cell { white-space: nowrap; width: 1%; }
    .btn-edit { color: #0f172a; border: none; background: transparent; padding: 0.25rem 0.5rem; }
    .btn-edit:hover { color: #3b82f6; }
    .nav-link { color: #bae6fd; margin-right: 15px; }
    .nav-link:hover, .nav-link.active { color: #e0f2fe; font-weight:600; }
    
    .total-box { font-size: 1.5rem; font-weight: 600; text-align: right; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #1e40af; color: #60a5fa; }
</style>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark mb-4">
        <div class="container">
            <a class="navbar-brand" href="#"><i class="fa-solid fa-file-invoice me-2"></i>HIX Invoices</a>
            <div class="collapse navbar-collapse">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item"><a class="nav-link active" href="facturas.prg">Invoices</a></li>
                    <li class="nav-item"><a class="nav-link" href="clientes.prg">Clients</a></li>
                    <li class="nav-item"><a class="nav-link" href="productos.prg">Products</a></li>
                </ul>
                <form class="d-flex" action="facturas.prg" method="POST">
                    <button class="btn btn-outline-success me-2" type="button" id="btnNew"><i class="fa-solid fa-plus me-1"></i>New</button>
                    <input type="hidden" name="action" value="search">
                    <input class="form-control me-2" type="search" name="search" placeholder="Search invoices..." value="{{VAL_SEARCH}}">
                    <button class="btn btn-outline-light me-2" type="submit">Search</button>
                    {{CANCEL_BUTTON}}
                    <a href="facturas_list_pdf.prg" target="_blank" class="btn btn-outline-info" title="Generar Listado PDF"><i class="fa-solid fa-file-pdf"></i></a>
                </form>
            </div>
        </div>
    </nav>

<div class="container">
    <div class="row">
        <!-- Left Side: Invoice Editor -->
         <div class="col-md-5 mb-4" id="invoiceFormCard" style="display:none;">
            <div class="card h-100">
                <div class="card-header">
                    <h5 class="mb-0"><i class="fa-solid fa-file-invoice me-2"></i>{{FORM_TITLE}}</h5>
                </div>
                <div class="card-body">
                    {{MESSAGE}}
                    <form method="POST" action="facturas.prg" onsubmit="prepareSubmit()">
                        <input type="hidden" name="action" value="save">
                        <input type="hidden" name="id" value="{{VAL_ID}}">
                        <input type="hidden" name="lines_data" id="lines_data"> 

                        <div class="mb-3">
                            <label class="form-label">Client</label>
                            <select name="client_id" class="form-select" required>
                                {{CLIENT_OPTIONS}}
                            </select>
                        </div>

                        <div class="row mb-3">
                            <div class="col-md-6">
                                <label class="form-label">Date</label>
                                <input type="date" name="date" class="form-control" value="{{VAL_DATE}}" required>
                            </div>
                            <div class="col-md-6">
                                <label class="form-label">Status</label>
                                <select name="status" class="form-select">
                                    <option value="Pending" {{SEL_PENDING}}>Pending</option>
                                    <option value="Paid" {{SEL_PAID}}>Paid</option>
                                    <option value="Cancelled" {{SEL_CANCELLED}}>Cancelled</option>
                                </select>
                            </div>
                        </div>

                        <h6 class="border-bottom border-secondary pb-2 mb-3 mt-4">Itens</h6>
                        <div class="table-responsive mb-3">
                            <table class="table table-sm" id="linesTable">
                                <thead>
                                    <tr>
                                        <th style="width: 45%;">Product</th>
                                        <th style="width: 20%;">Qty</th>
                                        <th style="width: 25%;">Price</th>
                                        <th style="width: 10%;"></th>
                                    </tr>
                                </thead>
                                <tbody id="linesBody">
                                    <!-- JS Rows -->
                                </tbody>
                            </table>
                        </div>
                        
                        <button type="button" class="btn btn-sm btn-secondary mb-3" onclick="addLine()">
                            <i class="fa-solid fa-plus me-1"></i> Add Item
                        </button>

                        <div class="total-box">
                            Total: $<span id="displayTotal">0.00</span>
                        </div>

                        <div class="d-grid gap-2 d-md-flex justify-content-md-end mt-4">
                            <button type="submit" class="btn btn-primary"><i class="fa-solid fa-save me-2"></i>Save Invoice</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>

        <!-- Right Side: List -->
        <div class="col-md-7" id="invoiceListColumn">
            <div class="card">
                 <div class="card-header d-flex justify-content-between align-items-center">
                    <h5 class="mb-0"><i class="fa-solid fa-list me-2"></i>Existing Invoices</h5>
                </div>
                <div class="card-body p-0">
                    <div class="table-responsive">
                        <table class="table table-hover">
                            <thead>
                                <tr>
                                    <th>ID</th>
                                    <th>Client</th>
                                    <th>Date</th>
                                    <th>Total</th>
                                    <th>Status</th>
                                    <th>Actions</th>
                                </tr>
                            </thead>
                            <tbody>
                                {{INVOICE_ROWS}}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    // Data injected from PRG
    {{PRODUCT_DATA}}
    // Format: var products = {"P1": {name: "Widget", price: 10}, ...};
    
    {{EXISTING_LINES}}
    // Format: var existingLines = [{prod_id: "P1", qty: 2}, ...];

    function renderLines() {
        const tbody = document.getElementById('linesBody');
        tbody.innerHTML = '';
        
        existingLines.forEach((line, index) => {
            addLineRow(line.prod_id, line.qty, index);
        });
        
        if(existingLines.length === 0) {
          // Add one empty row by default if new and register it
          existingLines.push({prod_id: "", qty: 1});
          addLineRow("", 1, 0);
        }
        
        calculateTotal();
    }

    function addLine() {
        addLineRow("", 1, existingLines.length);
        existingLines.push({prod_id: "", qty: 1});
    }
    
    function addLineRow(prodId, qty, index) {
        const body = document.getElementById('linesBody');
        const tr = document.createElement('tr');
        
        // Build Options
        let opts = '<option value="">Select...</option>';
        for (const [key, val] of Object.entries(products)) {
            const sel = (key === prodId) ? 'selected' : '';
            opts += `<option value="${key}" ${sel}>${val.name}</option>`;
        }
        
        tr.innerHTML = `
            <td>
                <select class="form-select form-select-sm" onchange="updateLine(${index}, 'prod_id', this.value); calculateTotal();">
                    ${opts}
                </select>
            </td>
            <td>
                <input type="number" class="form-control form-control-sm" value="${qty}" min="1" step="0.01" onchange="updateLine(${index}, 'qty', this.value); calculateTotal();">
            </td>
            <td id="price_${index}" class="text-end pe-3 pt-2">
                ${getProductPrice(prodId, qty)}
            </td>
            <td>
                <button type="button" class="btn btn-sm btn-outline-danger border-0" onclick="removeLine(${index})"><i class="fa-solid fa-times"></i></button>
            </td>
        `;
        body.appendChild(tr);
    }
    
    function getProductPrice(id, qty) {
        if(!id || !products[id]) return "0.00";
        return (products[id].price * qty).toFixed(2);
    }
    
    function updateLine(index, field, value) {
        if(existingLines[index]) {
            existingLines[index][field] = value;
            // Update specific row price display
            const rowPrice = document.getElementById(`price_${index}`);
            if(rowPrice) {
                rowPrice.innerText = getProductPrice(existingLines[index].prod_id, existingLines[index].qty);
            }
        }
    }
    
    function removeLine(index) {
        existingLines.splice(index, 1);
        renderLines(); // Re-render to update indices
    }
    
    function calculateTotal() {
        let total = 0;
        existingLines.forEach(line => {
            if(line.prod_id && products[line.prod_id]) {
                total += products[line.prod_id].price * line.qty;
            }
        });
        document.getElementById('displayTotal').innerText = total.toFixed(2);
    }
    
    function prepareSubmit() {
        // Serialize lines to format: ID,QTY;ID,QTY
        let data = "";
        existingLines.forEach(line => {
            if(line.prod_id) {
                data += `${line.prod_id},${line.qty};`;
            }
        });
        document.getElementById('lines_data').value = data;
    }

    // Initialize
    renderLines();
    
</script>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        var formCard = document.getElementById('invoiceFormCard');
        var btnNew = document.getElementById('btnNew');
        var inputId = document.querySelector('input[name="id"]');
        var listCol = document.getElementById('invoiceListColumn');
        
        function updateLayout() {
            if (formCard.style.display === "none") {
                listCol.classList.remove('col-md-7');
                listCol.classList.add('col-12');
            } else {
                listCol.classList.remove('col-12');
                listCol.classList.add('col-md-7');
            }
        }

        if (inputId && inputId.value.trim() !== "") {
            formCard.style.display = "block";
            updateLayout();
        } else {
            // Ensure initial state
            updateLayout();
        }

        if (btnNew) {
            btnNew.addEventListener('click', function(e) {
                e.preventDefault();
                if (formCard.style.display === "none") {
                    formCard.style.display = "block";
                } else {
                    formCard.style.display = "none";
                }
                updateLayout();
            });
        }
    });
</script>
</body>
</html>

factura_pdf.prg

function Main()
    local cId := Upost("id")
    local cTmpDir := "c:/hix/testdir"
    local cPdfFile := ""
    local cContent := ""
    local cBase64 := ""
    

// DB Vars
local cClientId := ""
local cDate := ""
local cStatus := ""
local nTotal := 0
local cCliName := ""
local cCliAddr := ""
local cCliCity := ""
local cCliZip := ""
local cCliVat := ""
local cCliPhone := ""

// Init Logic
cPdfFile := cTmpDir + "/invoice_" + cId + "_" + AllTrim(Str(Int(hb_Random() * 100000))) + ".pdf"

if Empty(cId)
UWrite("Error: No Invoice ID provided.")
return ""
endif

// Open Databases
if !OpenDatabases()
UWrite("Error opening databases.")
return ""
endif

// Find Invoice
select facturas
locate for AllTrim(id) == AllTrim(cId)
if !found()
UWrite("Error: Invoice not found.")
close all
return ""
endif

cClientId := facturas->client_id
cDate     := DToC(facturas->date)
cStatus   := facturas->status
nTotal    := facturas->total

// Find Client
select clients
locate for AllTrim(id) == AllTrim(cClientId)
if found()
cCliName  := AllTrim(clients->name)
cCliAddr  := AllTrim(clients->address)
cCliCity  := AllTrim(clients->city)
cCliZip   := AllTrim(clients->zip)
cCliVat   := AllTrim(clients->vat_id)
cCliPhone := AllTrim(clients->phone)
endif

// GENERATE PDF
GeneratePDF( cPdfFile, cId, cDate, cStatus, nTotal, cCliName, cCliAddr, cCliCity, cCliZip, cCliVat, cCliPhone )

close all

// READ AND OUTPUT
if File(cPdfFile)
cContent := MemoRead(cPdfFile)
cBase64  := hb_base64Encode(cContent)
    
UWrite( '<!DOCTYPE html>' )
UWrite( '<html><head><title>Factura ' + cId + '</title></head>' )
UWrite( '<body style="margin:0;padding:0;overflow:hidden;">' )
UWrite( '<iframe src="data:application/pdf;base64,' + cBase64 + '" width="100%" height="100%" style="border:none;height:100vh;"></iframe>' )
UWrite( '</body></html>' )
else
UWrite("Error: PDF Generation failed.")
endif

return ""

function GeneratePDF( cFile, cId, cDate, cStatus, nTotal, cName, cAddr, cCity, cZip, cVat, cPhone )
    local oPdf, oPage, oFont, oFontBold
    local nY := 750
    local nRow := 0
    local cProdName := ""
    local nLPrice := 0
    

oPdf := HPDF_New()
HPDF_SetCompressionMode( oPdf, "HPDF_COMP_ALL" )
oPage := HPDF_AddPage( oPdf )
HPDF_Page_SetSize( oPage, "HPDF_PAGE_SIZE_A4", "HPDF_PAGE_PORTRAIT" )

oFont := HPDF_GetFont( oPdf, "Helvetica", "CP1252" )
oFontBold := HPDF_GetFont( oPdf, "Helvetica-Bold", "CP1252" )

// HEADER
HPDF_Page_SetFontAndSize( oPage, oFontBold, 24 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 50, nY, "INVOICE" )
HPDF_Page_EndText( oPage )

HPDF_Page_SetFontAndSize( oPage, oFont, 12 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 400, nY, "Invoice #: " + cId )
HPDF_Page_TextOut( oPage, 400, nY - 15, "Date: " + cDate )
HPDF_Page_TextOut( oPage, 400, nY - 30, "Status: " + cStatus )
HPDF_Page_EndText( oPage )

nY -= 80

// CLIENT INFO
HPDF_Page_SetFontAndSize( oPage, oFontBold, 14 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 50, nY, "Bill To:" )
HPDF_Page_EndText( oPage )

nY -= 20
HPDF_Page_SetFontAndSize( oPage, oFont, 12 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 50, nY, cName )
nY -= 15
if !Empty(cVat); HPDF_Page_TextOut( oPage, 50, nY, "VAT ID: " + cVat ); nY -= 15; endif
if !Empty(cAddr); HPDF_Page_TextOut( oPage, 50, nY, cAddr ); nY -= 15; endif
if !Empty(cCity); HPDF_Page_TextOut( oPage, 50, nY, AllTrim(cCity) + " " + AllTrim(cZip) ); nY -= 15; endif
if !Empty(cPhone); HPDF_Page_TextOut( oPage, 50, nY, "Phone: " + cPhone ); nY -= 15; endif
HPDF_Page_EndText( oPage )

nY -= 40

// TABLE HEADER
HPDF_Page_SetLineWidth( oPage, 1 )
HPDF_Page_Rectangle( oPage, 50, nY, 500, 25 )
HPDF_Page_Stroke( oPage )

HPDF_Page_SetFontAndSize( oPage, oFontBold, 11 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 60, nY + 8, "Product" )
HPDF_Page_TextOut( oPage, 300, nY + 8, "Qty" )
HPDF_Page_TextOut( oPage, 380, nY + 8, "Price" )
HPDF_Page_TextOut( oPage, 480, nY + 8, "Total" )
HPDF_Page_EndText( oPage )

nY -= 5

// ITEMS
select lines
go top
HPDF_Page_SetFontAndSize( oPage, oFont, 11 )

do while !Eof()
if lines->invoice_id == cId
cProdName := "Unknown"
nLPrice := lines->price
select products
locate for id == lines->prod_id
if found(); cProdName := AllTrim(products->name); endif
select lines
        
nY -= 20
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 60, nY, cProdName )
HPDF_Page_TextOut( oPage, 300, nY, AllTrim(Str(lines->qty)) )
HPDF_Page_TextOut( oPage, 380, nY, AllTrim(Str(nLPrice)) )
HPDF_Page_TextOut( oPage, 480, nY, AllTrim(Str(lines->total)) )
HPDF_Page_EndText( oPage )
endif
skip
enddo

// GRAND TOTAL
nY -= 40
HPDF_Page_SetFontAndSize( oPage, oFontBold, 14 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 350, nY, "Grand Total: " + AllTrim(Str(nTotal)) )
HPDF_Page_EndText( oPage )

HPDF_SaveToFile( oPdf, cFile )
HPDF_Free( oPdf )
return nil

function OpenDatabases()
    local cTmpDir := "c:/hix/testdir"
    if Select("facturas") == 0; use (cTmpDir+"/facturas_v2.dbf") shared new alias "facturas"; endif
    if Select("lines")    == 0; use (cTmpDir+"/facturas_lines.dbf") shared new alias "lines"; endif
    if Select("clients")  == 0; use (cTmpDir+"/clientes_v2.dbf") shared new alias "clients"; endif
    if Select("products") == 0; use (cTmpDir+"/productos.dbf") shared new alias "products"; endif
return .T.

facturas_list_pdf.prg

function Main()
    local cTmpDir := "c:/hix/testdir"
    local cPdfFile := cTmpDir + "/invoices_list_" + AllTrim(Str(Int(hb_Random() * 100000))) + ".pdf"
    local cContent, cBase64

// Open Databases
if !OpenDatabases()
UWrite("Error opening databases.")
return ""
endif

// GENERATE PDF
GeneratePDF( cPdfFile )

close all

// READ AND OUTPUT
if File(cPdfFile)
cContent := MemoRead(cPdfFile)
cBase64  := hb_base64Encode(cContent)
    
UWrite( '<!DOCTYPE html>' )
UWrite( '<html><head><title>Listado de Facturas</title></head>' )
UWrite( '<body style="margin:0;padding:0;overflow:hidden;">' )
UWrite( '<iframe src="data:application/pdf;base64,' + cBase64 + '" width="100%" height="100%" style="border:none;height:100vh;"></iframe>' )
UWrite( '</body></html>' )
else
UWrite("Error: PDF Generation failed.")
endif

return ""

function GeneratePDF( cFile )
    local oPdf, oPage, oFont, oFontBold
    local nY := 750
    local nRow := 0
    local cCliName := ""
    local cCliId := ""
    

HPDF_SetCompressionMode( oPdf, "HPDF_COMP_ALL" )
oPage := HPDF_AddPage( oPdf )
HPDF_Page_SetSize( oPage, "HPDF_PAGE_SIZE_A4", "HPDF_PAGE_PORTRAIT" )

set deleted on

oFont := HPDF_GetFont( oPdf, "Helvetica", "CP1252" )
oFontBold := HPDF_GetFont( oPdf, "Helvetica-Bold", "CP1252" )

// HEADER
HPDF_Page_SetFontAndSize( oPage, oFontBold, 18 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 50, nY, "LISTADO DE FACTURAS" )
HPDF_Page_EndText( oPage )

nY -= 30

// TABLE HEADER
HPDF_Page_SetLineWidth( oPage, 1 )
HPDF_Page_Rectangle( oPage, 50, nY, 500, 20 )
HPDF_Page_Stroke( oPage )

HPDF_Page_SetFontAndSize( oPage, oFontBold, 10 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 55, nY + 6, "ID" )
HPDF_Page_TextOut( oPage, 120, nY + 6, "Date" )
HPDF_Page_TextOut( oPage, 200, nY + 6, "Client" )
HPDF_Page_TextOut( oPage, 380, nY + 6, "Status" )
HPDF_Page_TextOut( oPage, 460, nY + 6, "Total" )
HPDF_Page_EndText( oPage )

nY -= 5

// ITEMS
select facturas
go top
HPDF_Page_SetFontAndSize( oPage, oFont, 10 )

do while !Eof()
// Lookup Client Name
cCliName := "Unknown"
cCliId := facturas->client_id

select clients
go top
locate for AllTrim(clients->id) == AllTrim(cCliId)
if found()
cCliName := AllTrim(clients->name)
endif
select facturas
    
nY -= 15
if nY < 50
HPDF_Page_EndText( oPage )
oPage := HPDF_AddPage( oPdf )
HPDF_Page_SetSize( oPage, "HPDF_PAGE_SIZE_A4", "HPDF_PAGE_PORTRAIT" )
HPDF_Page_SetFontAndSize( oPage, oFont, 10 )
HPDF_Page_BeginText( oPage )
nY := 750
else
HPDF_Page_BeginText( oPage )
endif

HPDF_Page_TextOut( oPage, 55, nY, AllTrim(facturas->id) )
HPDF_Page_TextOut( oPage, 120, nY, DToC(facturas->date) )
HPDF_Page_TextOut( oPage, 200, nY, SubStr(cCliName, 1, 30) )
HPDF_Page_TextOut( oPage, 380, nY, AllTrim(facturas->status) )
HPDF_Page_TextOut( oPage, 460, nY, AllTrim(Str(facturas->total)) )
HPDF_Page_EndText( oPage )
    
skip
enddo

HPDF_SaveToFile( oPdf, cFile )
HPDF_Free( oPdf )
return nil

function OpenDatabases()
    local cTmpDir := "c:/hix/testdir"
    if Select("facturas") == 0; use (cTmpDir+"/facturas_v2.dbf") shared new alias "facturas"; endif
    if Select("clients")  == 0; use (cTmpDir+"/clientes_v2.dbf") shared new alias "clients"; endif
return .T.
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Sat Dec 20, 2025 11:56 AM

clientes.prg

function Main()
    

local cAction    := Upost( "action" )
local cId        := Upost( "id" )
local cName      := Upost( "name" )
local cVat       := Upost( "vat_id" )
local cAddress   := Upost( "address" )
local cCity      := Upost( "city" )
local cZip       := Upost( "zip" )
local cPhone     := Upost( "phone" )

local cSearch    := Upost( "search" )

local cTmpDir    := "c:/hix/testdir"
local cDbCli     := cTmpDir + "/clientes_v2.dbf"

local cMessage   := ""

// Ensure Directory Exists
if ! IsDirectory( cTmpDir )
DirMake( cTmpDir )
endif

// Initialize Database if not exists
if ! File( cDbCli )
// Migration: If v2 doesn't exist, Create it with new structure
DbCreate( cDbCli, { ;
    { "ID",      "C",   8, 0 }, ;
    { "NAME",    "C",  50, 0 }, ;
    { "VAT_ID",  "C",  20, 0 }, ;
    { "ADDRESS", "C", 100, 0 }, ;
    { "CITY",    "C",  50, 0 }, ;
    { "ZIP",     "C",  10, 0 }, ;
    { "PHONE",   "C",  20, 0 }  ;
    } )
endif

// Open Database
if Select("clients") == 0
use (cDbCli) shared new alias "clients"
else
select clients
endif

do case
case cAction == "save"
if !Empty(cName)
select clients
if Empty( cId ) 
// New Client
append blank
if !NetErr()
cId := GenerateID()
replace id with cId
replace name    with cName
replace vat_id  with cVat
replace address with cAddress
replace city    with cCity
replace zip     with cZip
replace phone   with cPhone
                    
commit
unlock
cMessage := "Client created successfully."
else
cMessage := "Error: Could not create client (Lock failed)."
endif
else
// Update existing
go top
locate for AllTrim(id) == AllTrim(cId)
if found()
if Rlock()
replace name    with cName
replace vat_id  with cVat
replace address with cAddress
replace city    with cCity
replace zip     with cZip
replace phone   with cPhone
                        
commit
unlock
cMessage := "Client updated successfully."
endif
endif
endif
endif
// Reset form
cId := "" 
cName := ""
cVat := ""
cAddress := ""
cCity := ""
cZip := ""
cPhone := ""
        
case cAction == "delete"
if ! Empty( cId )
select clients
go top
locate for AllTrim(id) == AllTrim(cId)
if found() .and. Rlock()
delete
unlock
commit
cMessage := "Client deleted successfully."
endif
endif
cId := ""
cName := ""

case cAction == "edit"
select clients
go top
locate for AllTrim(id) == AllTrim(cId)
if found()
cName    := AllTrim(clients->name)
cVat     := AllTrim(clients->vat_id)
cAddress := AllTrim(clients->address)
cCity    := AllTrim(clients->city)
cZip     := AllTrim(clients->zip)
cPhone   := AllTrim(clients->phone)
endif
        
case cAction == "cancel"
cId := "" 
cName := ""
cVat := ""
cAddress := ""
cCity := ""
cZip := ""
cPhone := ""
cSearch := ""
    
endcase

RenderView( cId, cName, cVat, cAddress, cCity, cZip, cPhone, cMessage, cSearch )

close all

return ""

function GenerateID()
    local cChars := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    local cNewId := "C"
    local i
    for i := 1 to 7
    cNewId += SubStr( cChars, Int( hb_Random() * Len(cChars) ) + 1, 1 )
    next
return cNewId

function RenderView( cId, cName, cVat, cAddress, cCity, cZip, cPhone, cMessage, cSearch )
    local cHtml := MemoRead( "c:/hix/clientes.view" )
    local cMsgHtml := ""
    

// Build Rows
local cRows := ""
local lMatch := .T.
local cInfo := ""
local cContact := ""

select clients
go top
do while ! Eof()
lMatch := .T.
    
if !Empty( cSearch )
if ! ( Upper(AllTrim(cSearch)) $ Upper(clients->name) ) .and. ;
    ! ( Upper(AllTrim(cSearch)) $ Upper(clients->city) )
lMatch := .F.
endif
endif

if lMatch
// Format Info Column
cInfo := ""
if !Empty(clients->city)
cInfo += AllTrim(clients->city)
endif
if !Empty(clients->vat_id)
if !Empty(cInfo); cInfo += "<br>"; endif
cInfo += "<small class='text-muted'>VAT: " + AllTrim(clients->vat_id) + "</small>"
endif
        
// Format Contact Column
cContact := ""
if !Empty(clients->phone)
cContact += "<i class='fa-solid fa-phone me-1'></i>" + AllTrim(clients->phone)
endif
        
cRows += [<tr>]
cRows += [<td>] + AllTrim(clients->id) + [</td>]
cRows += [<td><strong>] + AllTrim(clients->name) + [</strong></td>]
cRows += [<td>] + cInfo + [</td>]
cRows += [<td>] + cContact + [</td>]
cRows += [<td class="actions-cell text-end">]
cRows += [<form method="POST" action="clientes.prg" style="display:inline;">]
cRows += [<input type="hidden" name="action" value="edit">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(clients->id) + [">]
cRows += [<button type="submit" class="btn btn-sm btn-edit"><i class="fa-solid fa-pen"></i></button>]
cRows += [</form> ]
cRows += [<form method="POST" action="clientes.prg" style="display:inline;" onsubmit="return confirm('Delete client?')">]
cRows += [<input type="hidden" name="action" value="delete">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(clients->id) + [">]
cRows += [<button type="submit" class="btn btn-sm btn-edit" style="color:#ef4444;"><i class="fa-solid fa-trash"></i></button>]
cRows += [</form></td></tr>]
endif
skip
enddo

// Standard Fields
cHtml := StrTran( cHtml, "{{CLIENT_ROWS}}", cRows )
cHtml := StrTran( cHtml, "{{VAL_ID}}",      iif( Empty(cId), "", cId ) )
cHtml := StrTran( cHtml, "{{VAL_NAME}}",    iif( Empty(cName), "", cName ) )
cHtml := StrTran( cHtml, "{{VAL_VAT}}",     iif( Empty(cVat), "", cVat ) )
cHtml := StrTran( cHtml, "{{VAL_ADDRESS}}", iif( Empty(cAddress), "", cAddress ) )
cHtml := StrTran( cHtml, "{{VAL_CITY}}",    iif( Empty(cCity), "", cCity ) )
cHtml := StrTran( cHtml, "{{VAL_ZIP}}",     iif( Empty(cZip), "", cZip ) )
cHtml := StrTran( cHtml, "{{VAL_PHONE}}",   iif( Empty(cPhone), "", cPhone ) )

// Message & Search
if !Empty( cMessage )
if Left( cMessage, 5 ) == "Error"
cMsgHtml := [<div class="alert alert-danger" role="alert">] + cMessage + [</div>]
else
cMsgHtml := [<div class="alert alert-success" role="alert">] + cMessage + [</div>]
endif
endif
cHtml := StrTran( cHtml, "{{MESSAGE}}", cMsgHtml )
cHtml := StrTran( cHtml, "{{VAL_SEARCH}}", iif( Empty(cSearch), "", cSearch ) )

if !Empty(cId)
cHtml := StrTran( cHtml, "{{FORM_TITLE}}", "Edit Client: " + cId )
cHtml := StrTran( cHtml, "{{CANCEL_BUTTON}}", [<a href="clientes.prg" class="btn btn-sm" style="background:#1e3a8a;margin-left:5px;color:#e0f2fe;">Cancel</a>] )
else
cHtml := StrTran( cHtml, "{{FORM_TITLE}}", "New Client" )
cHtml := StrTran( cHtml, "{{CANCEL_BUTTON}}", "" )
endif

UWrite( cHtml )

return ""

clientes.view

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Clients Management</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
    <style>
        :root { --primary-bg: #0f172a; --card-bg: #1e293b; --text-main: #e0f2fe; --accent: #38bdf8; }
        body { background-color: var(--primary-bg); color: var(--text-main); font-family: 'Inter', sans-serif; }
        

    /* Navbar Standardization */
    .navbar { background-color: #172554; border-bottom: 1px solid #1e3a8a; padding-top: 0.75rem; padding-bottom: 0.75rem; min-height: 70px; }
    .navbar-brand { font-weight: 600; color: #bae6fd !important; font-size: 1.25rem; }
    .nav-link { color: #bfdbfe; margin-right: 15px; font-weight: 500; }
    .nav-link:hover, .nav-link.active { color: #e0f2fe; font-weight: 600; }
    
    .card { background-color: var(--card-bg); border: 1px solid #334155; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); margin-bottom: 2rem; }
    .card-header { background-color: #1e293b; border-bottom: 1px solid #334155; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: #bae6fd; padding: 1rem 1.25rem; }
    .form-label { font-size: 0.875rem; font-weight: 500; color: #bfdbfe; margin-bottom: 0.5rem; }
    .form-control, .form-select { background-color: #0f172a; border: 1px solid #334155; color: #e0f2fe; padding: 0.625rem; }
    .form-control:focus, .form-select:focus { background-color: #172554; border-color: #60a5fa; box-shadow: 0 0 0 2px rgba(96,165,250,0.3); color: #e0f2fe; }
    .btn-primary { background-color: #3b82f6; border: none; padding: 0.625rem 1.25rem; font-weight: 500; }
    .btn-primary:hover { background-color: #2563eb; }
    .table { color: #0c4a6e; margin-bottom: 0; }
    .table thead th { background-color: #1e3a8a; border-bottom: none; color: #fff; font-weight: 600; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.05em; padding: 1rem; }
    .table tbody tr:nth-of-type(odd) { background-color: #e0f2fe; color: #0c4a6e; }
    .table tbody tr:nth-of-type(even) { background-color: #bae6fd; color: #0c4a6e; }
    .table td { border-bottom: 1px solid #7dd3fc; padding: 1rem; vertical-align: middle; border-top: none; }
    .table tr:last-child td { border-bottom: none; }
    .actions-cell { white-space: nowrap; width: 1%; }
    .btn-edit { color: #0f172a; border: none; background: transparent; padding: 0.25rem 0.5rem; }
    .btn-edit:hover { color: #3b82f6; }
    .nav-link { color: #bae6fd; margin-right: 15px; }
    .nav-link:hover, .nav-link.active { color: #e0f2fe; font-weight:600; }
</style>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark mb-4">
        <div class="container">
            <a class="navbar-brand" href="#"><i class="fa-solid fa-users me-2"></i>HIX Clients</a>
            <div class="collapse navbar-collapse">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                   <li class="nav-item"><a class="nav-link" href="facturas.prg">Invoices</a></li>
                   <li class="nav-item"><a class="nav-link active" href="clientes.prg">Clients</a></li>
                   <li class="nav-item"><a class="nav-link" href="productos.prg">Products</a></li> 
                </ul>
                <form class="d-flex" action="clientes.prg" method="POST">
                    <button class="btn btn-outline-success me-2" type="button" id="btnNew"><i class="fa-solid fa-plus me-1"></i>New</button>
                    <input type="hidden" name="action" value="search">
                    <input class="form-control me-2" type="search" name="search" placeholder="Search clients..." value="{{VAL_SEARCH}}">
                    <button class="btn btn-outline-light me-2" type="submit">Search</button>
                    {{CANCEL_BUTTON}}
                    <a href="clientes_pdf.prg" target="_blank" class="btn btn-outline-info" title="Generar Listado PDF"><i class="fa-solid fa-file-pdf"></i></a>
                </form>
            </div>
        </div>
    </nav>

<div class="container">
    {{MESSAGE}}
    
    <div class="card" id="clientFormCard" style="display:none;">
        <div class="card-header">{{FORM_TITLE}}</div>
        <div class="card-body">
            <form method="POST" action="clientes.prg">
                <input type="hidden" name="action" value="save">
                <input type="hidden" name="id" value="{{VAL_ID}}">
                
                <div class="row g-3">
                    <div class="col-md-6">
                        <label class="form-label">Client Name</label>
                        <input type="text" class="form-control" name="name" value="{{VAL_NAME}}" required placeholder="Company or Full Name">
                    </div>
                    <div class="col-md-6">
                        <label class="form-label">VAT ID / Tax Number</label>
                        <input type="text" class="form-control" name="vat_id" value="{{VAL_VAT}}" placeholder="e.g. US123456789">
                    </div>
                    <div class="col-md-12">
                        <label class="form-label">Address</label>
                        <input type="text" class="form-control" name="address" value="{{VAL_ADDRESS}}" placeholder="Street address, P.O. box">
                    </div>
                    <div class="col-md-5">
                        <label class="form-label">City</label>
                        <input type="text" class="form-control" name="city" value="{{VAL_CITY}}" placeholder="City">
                    </div>
                    <div class="col-md-3">
                        <label class="form-label">Zip / Postal Code</label>
                        <input type="text" class="form-control" name="zip" value="{{VAL_ZIP}}" placeholder="Zip Code">
                    </div>
                    <div class="col-md-4">
                        <label class="form-label">Phone</label>
                        <input type="text" class="form-control" name="phone" value="{{VAL_PHONE}}" placeholder="+1 (555) 000-0000">
                    </div>
                    
                    <div class="col-12 text-end mt-4">
                        <button type="submit" class="btn btn-primary"><i class="fa-solid fa-save me-2"></i>Save Client</button>
                    </div>
                </div>
            </form>
        </div>
    </div>

    <div class="card">
        <div class="card-header"><i class="fa-solid fa-list me-2"></i> Client Directory</div>
        <div class="table-responsive">
            <table class="table table-hover align-middle">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Name</th>
                        <th>Info</th>
                        <th>Contact</th>
                        <th class="text-end">Actions</th>
                    </tr>
                </thead>
                <tbody>
                    {{CLIENT_ROWS}}
                </tbody>
            </table>
        </div>
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        var formCard = document.getElementById('clientFormCard');
        var btnNew = document.getElementById('btnNew');
        var inputId = document.querySelector('input[name="id"]');
        
        // Show form automatically if we are in Edit mode (ID is present) or if there is an error message (usually implies we want to retry save)
        // Checking {{VAL_ID}} via the input value
        if (inputId && inputId.value.trim() !== "") {
            formCard.style.display = "block";
        }

        btnNew.addEventListener('click', function(e) {
            e.preventDefault();
            // Toggle visibility
            if (formCard.style.display === "none") {
                formCard.style.display = "block";
                // Focus on name field
                var nameInput = document.querySelector('input[name="name"]');
                if(nameInput) nameInput.focus();
            } else {
                formCard.style.display = "none";
            }
        });
    });
</script>
</body>
</html>

clientes_pdf.prg

function Main()
    local cTmpDir := "c:/hix/testdir"
    local cPdfFile := cTmpDir + "/clients_list_" + AllTrim(Str(Int(hb_Random() * 100000))) + ".pdf"
    local cContent, cBase64

// Open Databases
if !OpenDatabases()
UWrite("Error opening databases.")
return ""
endif

// GENERATE PDF
GeneratePDF( cPdfFile )

close all

// READ AND OUTPUT
if File(cPdfFile)
cContent := MemoRead(cPdfFile)
cBase64  := hb_base64Encode(cContent)
    
UWrite( '<!DOCTYPE html>' )
UWrite( '<html><head><title>Listado de Clientes</title></head>' )
UWrite( '<body style="margin:0;padding:0;overflow:hidden;">' )
UWrite( '<iframe src="data:application/pdf;base64,' + cBase64 + '" width="100%" height="100%" style="border:none;height:100vh;"></iframe>' )
UWrite( '</body></html>' )
else
UWrite("Error: PDF Generation failed.")
endif

return ""

function GeneratePDF( cFile )
    local oPdf, oPage, oFont, oFontBold
    local nY := 750
    local nRow := 0
    

oPdf := HPDF_New()
HPDF_SetCompressionMode( oPdf, "HPDF_COMP_ALL" )
oPage := HPDF_AddPage( oPdf )
HPDF_Page_SetSize( oPage, "HPDF_PAGE_SIZE_A4", "HPDF_PAGE_PORTRAIT" )

oFont := HPDF_GetFont( oPdf, "Helvetica", "CP1252" )
oFontBold := HPDF_GetFont( oPdf, "Helvetica-Bold", "CP1252" )

// HEADER
HPDF_Page_SetFontAndSize( oPage, oFontBold, 18 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 50, nY, "LISTADO DE CLIENTES" )
HPDF_Page_EndText( oPage )

nY -= 30

// TABLE HEADER
HPDF_Page_SetLineWidth( oPage, 1 )
HPDF_Page_Rectangle( oPage, 50, nY, 500, 20 )
HPDF_Page_Stroke( oPage )

HPDF_Page_SetFontAndSize( oPage, oFontBold, 10 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 55, nY + 6, "ID" )
HPDF_Page_TextOut( oPage, 110, nY + 6, "Name" )
HPDF_Page_TextOut( oPage, 250, nY + 6, "City" )
HPDF_Page_TextOut( oPage, 350, nY + 6, "Phone" )
HPDF_Page_TextOut( oPage, 450, nY + 6, "VAT ID" )
HPDF_Page_EndText( oPage )

nY -= 5

// ITEMS
select clients
go top
HPDF_Page_SetFontAndSize( oPage, oFont, 10 )

do while !Eof()
nY -= 15
if nY < 50
HPDF_Page_EndText( oPage )
oPage := HPDF_AddPage( oPdf )
HPDF_Page_SetSize( oPage, "HPDF_PAGE_SIZE_A4", "HPDF_PAGE_PORTRAIT" )
HPDF_Page_SetFontAndSize( oPage, oFont, 10 )
HPDF_Page_BeginText( oPage )
nY := 750
else
HPDF_Page_BeginText( oPage )
endif

HPDF_Page_TextOut( oPage, 55, nY, AllTrim(clients->id) )
HPDF_Page_TextOut( oPage, 110, nY, SubStr(AllTrim(clients->name), 1, 25) )
HPDF_Page_TextOut( oPage, 250, nY, SubStr(AllTrim(clients->city), 1, 15) )
HPDF_Page_TextOut( oPage, 350, nY, AllTrim(clients->phone) )
HPDF_Page_TextOut( oPage, 450, nY, AllTrim(clients->vat_id) )
HPDF_Page_EndText( oPage )
    
skip
enddo

HPDF_SaveToFile( oPdf, cFile )
HPDF_Free( oPdf )
return nil

function OpenDatabases()
    local cTmpDir := "c:/hix/testdir"
    if Select("clients")  == 0; use (cTmpDir+"/clientes_v2.dbf") shared new alias "clients"; endif
return .T.
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Sat Dec 20, 2025 11:57 AM

productos.prg

function Main()
    

local cAction    := Upost( "action" )
local cId        := Upost( "id" )
local cName      := Upost( "name" )
local cPrice     := Upost( "price" )
local cStock     := Upost( "stock" )
local cSearch    := Upost( "search" )

local cTmpDir    := "c:/hix/testdir"
local cDbProd    := cTmpDir + "/productos.dbf"

local cMessage   := ""

// Ensure Directory Exists
if ! IsDirectory( cTmpDir )
DirMake( cTmpDir )
endif

// Initialize Database if not exists
if ! File( cDbProd )
DbCreate( cDbProd, { ;
    { "ID",    "C",  8, 0 }, ;
    { "NAME",  "C", 50, 0 }, ;
    { "PRICE", "N", 10, 2 }, ;
    { "STOCK", "N", 10, 2 } ;
    } )
endif

// Open Database
if Select("products") == 0
use (cDbProd) shared new alias "products"
else
select products
endif

do case
case cAction == "save"
if !Empty(cName)
select products
if Empty( cId ) 
// New Product
append blank
if !NetErr()
cId := GenerateID()
replace id with cId
replace name with cName
replace price with Val(cPrice)
replace stock with Val(cStock)
commit
unlock
cMessage := "Product created successfully."
else
cMessage := "Error: Could not create product (Lock failed)."
endif
else
// Update existing
go top
locate for AllTrim(id) == AllTrim(cId)
if found()
if Rlock()
replace name with cName
replace price with Val(cPrice)
replace stock with Val(cStock)
commit
unlock
cMessage := "Product updated successfully."
endif
endif
endif
endif
cId := "" 
cName := ""
cPrice := ""
cStock := ""
        
case cAction == "delete"
if ! Empty( cId )
select products
go top
locate for AllTrim(id) == AllTrim(cId)
if found() .and. Rlock()
delete
unlock
commit
cMessage := "Product deleted successfully."
endif
endif
cId := ""
cName := ""
cPrice := ""
cStock := ""

case cAction == "edit"
select products
go top
locate for AllTrim(id) == AllTrim(cId)
if found()
cName  := AllTrim(products->name)
cPrice := AllTrim(Str(products->price))
cStock := AllTrim(Str(products->stock))
endif
        
case cAction == "cancel"
cId := "" 
cName := ""
cPrice := ""
cStock := ""
cSearch := ""
    
endcase

RenderView( cId, cName, cPrice, cStock, cMessage, cSearch )

close all

return ""

function GenerateID()
    local cChars := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    local cNewId := "P"
    local i
    for i := 1 to 7
    cNewId += SubStr( cChars, Int( hb_Random() * Len(cChars) ) + 1, 1 )
    next
return cNewId

function RenderView( cId, cName, cPrice, cStock, cMessage, cSearch )
    local cHtml := MemoRead( "c:/hix/productos.view" )
    local cMsgHtml := ""
    

// Build Rows
local cRows := ""
local lMatch := .T.

select products
go top
do while ! Eof()
lMatch := .T.
    
if !Empty( cSearch )
if ! ( Upper(AllTrim(cSearch)) $ Upper(products->name) )
lMatch := .F.
endif
endif

if lMatch
cRows += [<tr>]
cRows += [<td>] + AllTrim(products->id) + [</td>]
cRows += [<td>] + AllTrim(products->name) + [</td>]
cRows += [<td>] + AllTrim(Str(products->price)) + [</td>]
cRows += [<td>] + AllTrim(Str(products->stock)) + [</td>]
cRows += [<td class="actions-cell">]
cRows += [<form method="POST" action="productos.prg" style="display:inline;">]
cRows += [<input type="hidden" name="action" value="edit">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(products->id) + [">]
cRows += [<button type="submit" class="btn btn-sm btn-edit"><i class="fa-solid fa-pen"></i></button>]
cRows += [</form> ]
cRows += [<form method="POST" action="productos.prg" style="display:inline;" onsubmit="return confirm('Delete product?')">]
cRows += [<input type="hidden" name="action" value="delete">]
cRows += [<input type="hidden" name="id" value="] + AllTrim(products->id) + [">]
cRows += [<button type="submit" class="btn btn-sm btn-danger"><i class="fa-solid fa-trash"></i></button>]
cRows += [</form></td></tr>]
endif
skip
enddo

// Standard Fields
cHtml := StrTran( cHtml, "{{PRODUCT_ROWS}}", cRows )
cHtml := StrTran( cHtml, "{{VAL_ID}}", iif( Empty(cId), "", cId ) )
cHtml := StrTran( cHtml, "{{VAL_NAME}}", iif( Empty(cName), "", cName ) )
cHtml := StrTran( cHtml, "{{VAL_PRICE}}", iif( Empty(cPrice), "", cPrice ) )
cHtml := StrTran( cHtml, "{{VAL_STOCK}}", iif( Empty(cStock), "", cStock ) )

// Message & Search
if !Empty( cMessage )
if Left( cMessage, 5 ) == "Error"
cMsgHtml := [<div class="alert alert-danger" role="alert">] + cMessage + [</div>]
else
cMsgHtml := [<div class="alert alert-success" role="alert">] + cMessage + [</div>]
endif
endif
cHtml := StrTran( cHtml, "{{MESSAGE}}", cMsgHtml )
cHtml := StrTran( cHtml, "{{VAL_SEARCH}}", iif( Empty(cSearch), "", cSearch ) )

if !Empty(cId)
cHtml := StrTran( cHtml, "{{FORM_TITLE}}", "Edit Product: " + cId )
cHtml := StrTran( cHtml, "{{CANCEL_BUTTON}}", [<a href="productos.prg" class="btn btn-sm" style="background:#1e3a8a;margin-left:5px;color:#e0f2fe;">Cancel</a>] )
else
cHtml := StrTran( cHtml, "{{FORM_TITLE}}", "New Product" )
cHtml := StrTran( cHtml, "{{CANCEL_BUTTON}}", "" )
endif

UWrite( cHtml )

return ""

productos.view

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Products Management</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <style>
        body { background-color: #0f172a; color: #e0f2fe; font-family: 'Inter', sans-serif; }
        .container-fluid { max-width: 1400px; padding: 20px; }
        .card { background-color: #1e293b; border: 1px solid #3b82f6; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.2); }
        .card-header { background-color: #1d4ed8; color: #e0f2fe; border-bottom: 1px solid #1e40af; border-radius: 12px 12px 0 0 !important; padding: 1rem 1.5rem; }
        

    /* Navbar Standardization */
    .navbar { background-color: #172554; border-bottom: 1px solid #1e3a8a; padding-top: 0.75rem; padding-bottom: 0.75rem; min-height: 70px; }
    .navbar-brand { font-weight: 600; color: #bae6fd !important; font-size: 1.25rem; }
    .nav-link { color: #bfdbfe; margin-right: 15px; font-weight: 500; }
    .nav-link:hover, .nav-link.active { color: #e0f2fe; font-weight: 600; }
    
    .form-label { color: #bfdbfe; font-weight: 500; font-size: 0.875rem; }
    .form-control, .form-select { background-color: #0f172a; border: 1px solid #3b82f6; color: #e0f2fe; border-radius: 8px; padding: 0.625rem 1rem; }
    .form-control:focus, .form-select:focus { background-color: #172554; border-color: #60a5fa; box-shadow: 0 0 0 2px rgba(96,165,250,0.3); color: #e0f2fe; }
    .btn-primary { background-color: #3b82f6; border: none; padding: 0.625rem 1.25rem; font-weight: 500; }
    .btn-primary:hover { background-color: #2563eb; }
    .table { color: #0c4a6e; margin-bottom: 0; }
    .table thead th { background-color: #1e3a8a; border-bottom: none; color: #fff; font-weight: 600; text-transform: uppercase; font-size: 0.75rem; letter-spacing: 0.05em; padding: 1rem; }
    .table tbody tr:nth-of-type(odd) { background-color: #e0f2fe; color: #0c4a6e; }
    .table tbody tr:nth-of-type(even) { background-color: #bae6fd; color: #0c4a6e; }
    .table td { border-bottom: 1px solid #7dd3fc; padding: 1rem; vertical-align: middle; border-top: none; }
    .table tr:last-child td { border-bottom: none; }
    .actions-cell { white-space: nowrap; width: 1%; }
    .btn-edit { color: #0f172a; border: none; background: transparent; padding: 0.25rem 0.5rem; }
    .btn-edit:hover { color: #3b82f6; }
    .nav-link { color: #bae6fd; margin-right: 15px; }
    .nav-link:hover, .nav-link.active { color: #e0f2fe; font-weight:600; }
</style>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark mb-4">
        <div class="container">
            <a class="navbar-brand" href="#"><i class="fa-solid fa-boxes-stacked me-2"></i>HIX Products</a>
            <div class="collapse navbar-collapse">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item"><a class="nav-link" href="facturas.prg">Invoices</a></li>
                    <li class="nav-item"><a class="nav-link" href="clientes.prg">Clients</a></li>
                    <li class="nav-item"><a class="nav-link active" href="productos.prg">Products</a></li>
                </ul>
                <form class="d-flex" action="productos.prg" method="POST">
                    <button class="btn btn-outline-success me-2" type="button" id="btnNew"><i class="fa-solid fa-plus me-1"></i>New</button>
                    <input type="hidden" name="action" value="search">
                    <input class="form-control me-2" type="search" name="search" placeholder="Search products..." value="{{VAL_SEARCH}}">
                    <button class="btn btn-outline-light me-2" type="submit">Search</button>
                    {{CANCEL_BUTTON}}
                    <a href="productos_pdf.prg" target="_blank" class="btn btn-outline-info" title="Generar Listado PDF"><i class="fa-solid fa-file-pdf"></i></a>
                </form>
            </div>
        </div>
    </nav>

<div class="container">
    <div class="row">
        <!-- Left Side: Form -->
        <div class="col-md-4 mb-4" id="productFormCard" style="display:none;">
            <div class="card h-100">
                <div class="card-header">
                    <h5 class="mb-0"><i class="fa-solid fa-box-open me-2"></i>{{FORM_TITLE}}</h5>
                </div>
                <div class="card-body">
                    {{MESSAGE}}
                    <form method="POST" action="productos.prg">
                        <input type="hidden" name="action" value="save">
                        <input type="hidden" name="id" value="{{VAL_ID}}">
                        
                        <div class="mb-3">
                            <label class="form-label">Product Name</label>
                            <input type="text" name="name" class="form-control" value="{{VAL_NAME}}" required placeholder="Enter product name">
                        </div>
                        
                        <div class="row">
                            <div class="col-md-6 mb-3">
                                <label class="form-label">Price</label>
                                <input type="number" step="0.01" name="price" class="form-control" value="{{VAL_PRICE}}" required>
                            </div>
                            <div class="col-md-6 mb-3">
                                <label class="form-label">Stock</label>
                                <input type="number" step="0.01" name="stock" class="form-control" value="{{VAL_STOCK}}" required>
                            </div>
                        </div>
                        
                        <div class="d-grid gap-2 d-md-flex justify-content-md-end mt-4">
                            <button type="submit" class="btn btn-primary"><i class="fa-solid fa-save me-2"></i>Save Product</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>

        <!-- Right Side: List -->
        <div class="col-md-8" id="productListColumn">
            <div class="card">
                <div class="card-header d-flex justify-content-between align-items-center">
                    <h5 class="mb-0"><i class="fa-solid fa-boxes me-2"></i>Inventory</h5>
                </div>
                <div class="card-body p-0">
                    <div class="table-responsive">
                        <table class="table table-hover">
                            <thead>
                                <tr>
                                    <th>ID</th>
                                    <th>Name</th>
                                    <th>Price</th>
                                    <th>Stock</th>
                                    <th>Actions</th>
                                </tr>
                            </thead>
                            <tbody>
                                {{PRODUCT_ROWS}}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        var formCard = document.getElementById('productFormCard');
        var btnNew = document.getElementById('btnNew');
        var inputId = document.querySelector('input[name="id"]');
        var listCol = document.getElementById('productListColumn');
        
        function updateLayout() {
            if (formCard.style.display === "none") {
                listCol.classList.remove('col-md-8');
                listCol.classList.add('col-12');
            } else {
                listCol.classList.remove('col-12');
                listCol.classList.add('col-md-8');
            }
        }

        if (inputId && inputId.value.trim() !== "") {
            formCard.style.display = "block";
            updateLayout();
        } else {
            updateLayout();
        }

        if (btnNew) {
            btnNew.addEventListener('click', function(e) {
                e.preventDefault();
                if (formCard.style.display === "none") {
                    formCard.style.display = "block";
                } else {
                    formCard.style.display = "none";
                }
                updateLayout();
            });
        }
    });
</script>
</body>
</html>

productos_pdf.prg

function Main()
    local cTmpDir := "c:/hix/testdir"
    local cPdfFile := cTmpDir + "/products_list_" + AllTrim(Str(Int(hb_Random() * 100000))) + ".pdf"
    local cContent, cBase64

// Open Databases
if !OpenDatabases()
UWrite("Error opening databases.")
return ""
endif

// GENERATE PDF
GeneratePDF( cPdfFile )

close all

// READ AND OUTPUT
if File(cPdfFile)
cContent := MemoRead(cPdfFile)
cBase64  := hb_base64Encode(cContent)
    
UWrite( '<!DOCTYPE html>' )
UWrite( '<html><head><title>Listado de Productos</title></head>' )
UWrite( '<body style="margin:0;padding:0;overflow:hidden;">' )
UWrite( '<iframe src="data:application/pdf;base64,' + cBase64 + '" width="100%" height="100%" style="border:none;height:100vh;"></iframe>' )
UWrite( '</body></html>' )
else
UWrite("Error: PDF Generation failed.")
endif

return ""

function GeneratePDF( cFile )
    local oPdf, oPage, oFont, oFontBold
    local nY := 750
    local nRow := 0
    

oPdf := HPDF_New()
HPDF_SetCompressionMode( oPdf, "HPDF_COMP_ALL" )
oPage := HPDF_AddPage( oPdf )
HPDF_Page_SetSize( oPage, "HPDF_PAGE_SIZE_A4", "HPDF_PAGE_PORTRAIT" )

oFont := HPDF_GetFont( oPdf, "Helvetica", "CP1252" )
oFontBold := HPDF_GetFont( oPdf, "Helvetica-Bold", "CP1252" )

// HEADER
HPDF_Page_SetFontAndSize( oPage, oFontBold, 18 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 50, nY, "LISTADO DE PRODUCTOS" )
HPDF_Page_EndText( oPage )

nY -= 30

// TABLE HEADER
HPDF_Page_SetLineWidth( oPage, 1 )
HPDF_Page_Rectangle( oPage, 50, nY, 500, 20 )
HPDF_Page_Stroke( oPage )

HPDF_Page_SetFontAndSize( oPage, oFontBold, 10 )
HPDF_Page_BeginText( oPage )
HPDF_Page_TextOut( oPage, 55, nY + 6, "ID" )
HPDF_Page_TextOut( oPage, 120, nY + 6, "Name" )
HPDF_Page_TextOut( oPage, 350, nY + 6, "Price" )
HPDF_Page_TextOut( oPage, 450, nY + 6, "Stock" )
HPDF_Page_EndText( oPage )

nY -= 5

// ITEMS
select products
go top
HPDF_Page_SetFontAndSize( oPage, oFont, 10 )

do while !Eof()
nY -= 15
if nY < 50
HPDF_Page_EndText( oPage )
oPage := HPDF_AddPage( oPdf )
HPDF_Page_SetSize( oPage, "HPDF_PAGE_SIZE_A4", "HPDF_PAGE_PORTRAIT" )
HPDF_Page_SetFontAndSize( oPage, oFont, 10 )
HPDF_Page_BeginText( oPage )
nY := 750
else
HPDF_Page_BeginText( oPage )
endif

HPDF_Page_TextOut( oPage, 55, nY, AllTrim(products->id) )
HPDF_Page_TextOut( oPage, 120, nY, SubStr(AllTrim(products->name), 1, 40) )
HPDF_Page_TextOut( oPage, 350, nY, AllTrim(Str(products->price)) )
HPDF_Page_TextOut( oPage, 450, nY, AllTrim(Str(products->stock)) )
HPDF_Page_EndText( oPage )
    
skip
enddo

HPDF_SaveToFile( oPdf, cFile )
HPDF_Free( oPdf )
return nil

function OpenDatabases()
    local cTmpDir := "c:/hix/testdir"
    if Select("products") == 0; use (cTmpDir+"/productos.dbf") shared new alias "products"; endif
return .T.
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 141
Joined: Fri Feb 15, 2019 01:37 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Sat Dec 20, 2025 02:29 PM

Hola a todos

Excelente

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Sat Dec 20, 2025 06:02 PM

https://ai.fivetechsoft.com/facturas.prg

Implementado al 100% con HIX y Antigravity :wink: :!:

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 141
Joined: Fri Feb 15, 2019 01:37 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Sat Dec 20, 2025 09:08 PM

Hola Antonio.

Lo estuve probando, todo se ven bien excelente, solo que en la parte de New Invoice, al salvar sale un error asi:

System Error

error DBFCDX/EG_UNLOCKED(38) 1022 Descripcion Lock required

Pero el resto todo bien

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Sun Dec 21, 2025 02:33 AM

Corregido 👍🏻

Gracias por el feedback 🙏🏻🤗

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Sun Dec 21, 2025 08:07 AM
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 1445
Joined: Mon Oct 10, 2005 02:38 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Sun Dec 21, 2025 09:31 PM

Antonio,

Llevo desde anteayer jugando con Antigravity, creo que me he comido el límite de uso de Gemini3, es posible? Le he pedido lo mismo con modificaciones muchas veces. También le he pedido modificaciones sobre lo que ha creado. Es un no parar. Con que que facilidad se obtienen resultados. Estoy usando Gemini 3 Pro High.

Un Saludo

Carlos G.



FiveWin 25.12 + Harbour 3.2.0dev (r2502110321), BCC 7.7 Windows 11 Home

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Mon Dec 22, 2025 02:24 AM

Carlos,

No se exactamente cual es su límite, pues yo uso una cuenta Pro obtenida con email de estudiante (oferta que terminó el 9 de diciembre). Le he preguntado a Grok:

Google Antigravity es una plataforma de desarrollo agentic (IDE con agentes IA autónomos) lanzada por Google junto con el modelo Gemini 3 (noviembre 2025). Es gratuita en su versión pública preview y permite usar Gemini 3 Pro con límites de uso generosos diseñados para que la mayoría de usuarios no los alcancen.Los límites se reinician cada 5 horas.
Google indica que solo una pequeña fracción de usuarios "power users" (con tareas muy intensas y complejas) llega a agotarlos.
Usuarios con suscripciones Google AI Pro o Ultra tienen límites más altos en Antigravity (así como en otras herramientas como Gemini CLI o Code Assist).

No se publican números exactos de prompts o tokens por ventana (varían según la complejidad de las tareas agenticas), pero en la práctica, sesiones normales duran horas sin problemas, mientras que usos intensos pueden agotar la cuota en 1-2 horas.Para detalles actualizados o descargar Antigravity, visita el sitio oficial: antigravity.google. Si tienes una suscripción paga, los límites son notablemente más amplios.

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 2
Joined: Fri Nov 12, 2021 02:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Mon Dec 22, 2025 08:54 PM

Saludos y gracias por la galeria de ejemplos.

En el modpro, al parecer falta el archivo: views/body.view

Salu2, GVARONAS

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Tue Dec 23, 2025 06:10 AM
gvaronas wrote:

Saludos y gracias por la galeria de ejemplos.

En el modpro, al parecer falta el archivo:
views/body.view

Salu2,
GVARONAS

views/body.view

<body>
	<div id="main" class="container-fluid" style="padding:0px;overflow-x:hidden">
		<div class="row">
			<div class="col-md-12">
				{{View("menu")}}
			</div>
		</div>
		<div id="body">
		   {{View("code")}}
			<div id="right">
         	<div id="splitter"></div>
         	<div id="output"></div>
      	</div> 
        <div id="chat-splitter"></div>
        <div id="chat-panel-container">
            {{View("chat")}}
        </div>
		</div>
	</div>
	<script>
      var editor_width, right_margin_left, cResult;

  editor_width = sessionStorage.getItem( 'editor-width' );
  if( editor_width )
     $( "#editor" ).css( "width", editor_width );

  right_margin_left = sessionStorage.getItem( 'right-margin-left' );
  if( right_margin_left )
      $( "#right" ).css( "margin-left", right_margin_left );

	cResult = sessionStorage.getItem( 'result' );
	if( cResult )
	   $('#output').html( cResult );	 

  $('#splitter').mousedown( function(e) {
      e.preventDefault();
      $(document).mousemove( function(e) {
         var x = e.pageX;
         e.preventDefault();
         if( x > 300 && x < ( $(window).width() - 300 ) )
         {
            $( "#editor" ).css( "width", x );
            $( "#right" ).css( "margin-left", x ); 
	sessionStorage.setItem( 'modpro_code', editor.getValue() );
            sessionStorage.setItem( 'editor-width', $( "#editor" ).css( "width" ) );
            sessionStorage.setItem( 'right-margin-left', $( "#right" ).css( "margin-left" ) );   
         }
      } ) } );
 
   $(document).mouseup( function() {
      $(document).unbind('mousemove'); } );
</script>
</body>
</html>
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Wed Dec 24, 2025 09:00 AM

https://ai.fivetechsoft.com/facturas_mysql.prg

100% coded with HIX + Antigravity

Full mysql support :wink: :!:

regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 476
Joined: Sat Feb 03, 2007 06:36 AM
Re: HIX &amp; AntiGravity apps gallery
Posted: Thu Dec 25, 2025 04:03 PM

Hola a todos, Feliz Navidad!

Antonio, y que utilizaste para trabajar Mysql?

Saludos cordiales.

Carlos

Posts: 44158
Joined: Thu Oct 06, 2005 05:47 PM
Re: HIX &amp; AntiGravity apps gallery
Posted: Thu Dec 25, 2025 07:13 PM

Carlos,

Le dí el código que usabamos en mod_harbour y además le pedí a Charly que construyese HIX con la función PTRTOSTR()
necesaria para convertir punteros a cadenas, y Antigravity hizo el resto :wink:

La idea es proporcionaros todo el código para que podais usarlo, pero como te digo, es preciso una actualización de HIX.

Espectacular como Antigravity portó toda la app que usaba DBFs a mysql :!: :o

regards, saludos

Antonio Linares
www.fivetechsoft.com