Passa al contenuto principale

Implementazione di tabelle e grafici

In questo tutorial creeremo l'interfaccia e la logica di un'applicazione semplice, con particolare attenzione alla visualizzazione dei dati. Lo sviluppo del progetto tratterà i seguenti argomenti:

  • La creazione di "Formulari", "Campi" e "Relazioni" per la strutturazione del database.
  • La costruzione di "Servizi Web REST" per fornire i dati e la visualizzazione dinamica delle informazioni in tabelle e grafici nell'applicazione, utilizzando le librerie React ChartJS 2 e la dipendenza Chart.js, oltre al componente tabella di Ant Design.

Tutta la struttura visiva del progetto sarà sviluppata utilizzando HTML, JavaScript e React.js. È consigliabile avere conoscenze di base di:

  • JavaScript
  • React.js
  • HTML

💡 In caso di dubbi su questi passaggi, consulta:

Prodotto Finale​

Seguendo tutti i passaggi di questo tutorial, costruirai un pannello funzionale nell'area di lavoro di Netuno, contenente:

  • Una tabella dati che elenca i record dei lavoratori e i totali delle ore.
  • Un grafico a barre dinamico e colorato che mostra visivamente questi stessi dati.

prodotto-finale-it.png

Architettura della Soluzione​

Prima di scrivere qualsiasi codice, è importante capire come si incastrano i pezzi. La soluzione è divisa in 4 livelli che comunicano dall'alto verso il basso:

DashboardContainer (recupera i dati dal server)
│
▼
VisualizzazioneDati (organizza il layout in due colonne)
│
┌────┴────┐
▼ ▼
ElencoTabella GraficoBarre (renderizzano i dati ricevuti)
suggerimento

Il vantaggio di questa architettura è che ogni componente ha una sola responsabilità.

Passo 1: Struttura dei Dati​

Il primo passo è creare i formulari, configurare la relazione tra di essi e compilarli con dati per generare la tabella e il grafico più avanti.

Questo è il diagramma con l'architettura dei formulari e i campi che dovrai replicare nel tuo ambiente Netuno:

primo-passo-it.png

Importante: Sebbene il campo id compaia nel diagramma, non è necessario crearlo manualmente. Netuno genera questo identificatore automaticamente subito dopo la creazione della tabella.

Template della Dashboard​

Per visualizzare la tabella e il grafico, avremo bisogno di un punto nell'interfaccia della tua applicazione. Netuno mette già a disposizione un template di base per il pannello principale.

Apri il tuo editor di codice e trova il file:

/apps/miaapp/server/templates/dashboard.html

(Ricorda di sostituire miaapp con il nome reale della cartella della tua applicazione).

Noterai la seguente riga di codice HTML:

<div id="app-dashboard"></div>

Passo 2: Servizio Dati​

Ora che abbiamo il database pronto e l'HTML preparato, dobbiamo creare il "messaggero" (un Servizio Web REST). Sarà responsabile di andare al database, calcolare il totale delle ore di ogni lavoratore e consegnare questa informazione pronta per la tabella e il grafico.

Creazione del file del servizio​

I servizi in Netuno sono conservati nella cartella server/services/. Vai alla directory del tuo progetto e crea il file lavoratori.js:

/apps/miaapp/server/services/lavoratori.js

Il DashboardContainer rileva la lingua del pannello e chiama /services/lavoratori quando il pannello è in Italiano, o /services/workers quando è in Inglese. Ogni lingua ha il proprio file di servizio.

Scrittura del codice​

Questo codice esegue tre azioni principali utilizzando le risorse native di Netuno (_db, _val e _out):

  • Query _db.query: Cerca solo i lavoratori attivi nel database e calcola la somma delle ore lavorate da ciascuno.

  • Elaborazione _val.list e _val.map: Organizza il risultato in una lista pulita con oggetti strutturati.

  • Risposta _out.json: Invia i dati in formato JSON affinché l'interfaccia (React) possa leggerli.

Apri il file lavoratori.js e incolla il codice seguente:

// 1. Query, _db.query() esegue l'SQL e restituisce l'elenco dei record trovati.
const righeDb = _db.query(`
SELECT DISTINCT
lavoratore.nome, SUM(DATEDIFF(HOUR, registro.inizio, registro.fine)) AS totale
FROM lavoratore INNER JOIN registro
ON lavoratore.id = registro.lavoratore_id
WHERE lavoratore.attivo = true AND registro.attivo = true
GROUP BY lavoratore.nome
ORDER BY totale ASC
`);

// 2. Elaborazione dei Dati, _val.list() crea una lista vuota per memorizzare i dati elaborati.
const lista = _val.list();

// Itera su ogni riga restituita dal database
for (const rigaDb of righeDb) {
lista.add(
// _val.map() crea un oggetto con i campi che React utilizzerà
_val.map()
.set("nome", rigaDb.getString("nome")) // nome → campo "nome"
.set("totale", rigaDb.getInt("totale")) // totale ore → campo "totale"
);
}

// 3. Risposta
// Invia la lista finale in formato JSON all'interfaccia React.
_out.json(lista);
suggerimento
  • Attenzione ai nomi dei campi: I campi definiti qui come "nome" e "totale" sono esattamente gli stessi che i componenti React cercheranno nei passaggi successivi (dataIndex: 'nome' e dataIndex: 'totale'). Devono essere identici.

  • Informazioni sul filtro attivo = true: Il database possiede una colonna attivo nelle tabelle lavoratore e registro. Filtrando solo i record attivi, garantiamo che i dati archiviati o disattivati non compaiano nella tabella e nel grafico.

Passo 3: Ambiente Frontend (React)​

Ora che il backend (database e servizi) è pronto, prepariamo l'interfaccia visiva.

Apri il terminale e segui i passaggi seguenti:

1. Installare le dipendenze di base​

Vai alla cartella ui della tua applicazione e installa i pacchetti.

npm install

2. Installare le librerie per i Grafici​

Poiché il progetto mostrerà grafici, scarica le librerie che realizzeranno questo disegno sullo schermo:

npm install react-chartjs-2 chart.js --save

3. Gestire più lingue​

Installa la libreria per gestire più lingue:

npm install react-intl

4. Avviare il Compilatore (Watch)​

Con tutto installato, ogni volta che salviamo un file, aggiornerà automaticamente lo schermo:

npm run watch

5. Creare Componenti​

Per mantenere il progetto organizzato, crea componenti separati per la Tabella e per il Grafico.

Vai alla directory src/components/ della tua applicazione e crea le cartelle:

  • Tabella: ElencoTabella
  • Grafico: GraficoBarre
  • Container: VisualizzazioneDati
/apps/nome_della_tua_app/ui/src/components/ElencoTabella/
/apps/nome_della_tua_app/ui/src/components/GraficoBarre/
/apps/nome_della_tua_app/ui/src/containers/VisualizzazioneDati/

Passo 4: Componente ElencoTabella​

Il componente ElencoTabella riceverà i dati dal servizio e costruirà automaticamente le colonne usando Ant Design — una libreria di componenti visivi pronti che accompagna già l'interfaccia di Netuno.

Vai alla cartella ElencoTabella e crea un file index.jsx:

/apps/nome_della_tua_app/ui/src/components/ElencoTabella/index.jsx

Apri il file e incolla il codice seguente:

import React from 'react';

import { FormattedMessage } from 'react-intl';

import { Table } from 'antd'; // Componente Tabella di Ant Design

import './index.less';

const messages = 'dashboardcontainer.visualizzazionedati.tabella';

function ElencoTabella({ dati = [] }) {
// Definizione delle intestazioni e quale campo dell'oggetto mostrare in ogni colonna
const colonne = [
{
title: <FormattedMessage id={`${messages}.lavoratori`} />, // Titolo tradotto
dataIndex: 'nome', // Campo "nome" del JSON restituito dal servizio
},
{
title: <FormattedMessage id={`${messages}.totale`} />,
dataIndex: 'totale', // Campo "totale" del JSON restituito dal servizio
}
];

return (
<Table
columns={colonne} // Struttura delle colonne
dataSource={dati} // I dati stessi (lista di oggetti)
pagination={false} // Senza paginazione: tutti i record in una volta
bordered={true} // Con bordi visibili nelle celle
/>
);
}

export default ElencoTabella;

Capire il codice:

Questo componente funzionale riceve la lista dati come parametro (prop) e la consegna direttamente al <Table> di Ant Design.

La configurazione colonne informa ad Ant Design quale titolo mostrare nell'intestazione title e quale campo dell'oggetto JSON inserire nella cella dataIndex. Ant Design gestisce automaticamente tutto l'HTML, gli stili dei bordi e il layout — non è necessario creare CSS manuale per questo.

File index.less: Crea il file index.less nella stessa cartella. Per ora può rimanere vuoto, poiché Ant Design fornisce già lo stile di base della tabella.

Passo 5: Componente GraficoBarre​

Creeremo un Grafico a Barre per la visualizzazione dei dati utilizzando la libreria react-chartjs-2. Questo componente riceverà i dati, li trasformerà nel formato compreso da Chart.js, assegnerà i colori e disegnerà il grafico sullo schermo.

Vai alla cartella GraficoBarre e crea un file chiamato index.jsx:

/apps/nome_della_tua_app/ui/src/components/GraficoBarre/index.jsx

Apri il file e incolla il codice seguente:

import React, { useState, useEffect } from 'react';
import { Bar } from 'react-chartjs-2'; // Componente grafico a barre
import { Chart, registerables } from 'chart.js'; // Nucleo di Chart.js

import { useIntl } from 'react-intl';

import './index.less';

const messages = 'dashboardcontainer.visualizzazionedati.grafico';

// OBBLIGATORIO: registrare tutti gli elementi di Chart.js prima dell'uso.
// Senza questa riga il grafico non viene renderizzato e genera un errore nella console.
Chart.register(...registerables);

function GraficoBarre({ dati }) {
const intl = useIntl();
const [datiGrafico, setDatiGrafico] = useState(null); // Dati nel formato di Chart.js

useEffect(() => {
// Palette di 7 colori per le barre (ciclica se ci sono più di 7 elementi)
const colori = [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
'rgb(201, 203, 207)'
];

const etichette = [];
const valori = [];

// Struttura dati richiesta da Chart.js
const nuoviDatiGrafico = {
labels: [], // Nomi sull'asse X (orizzontale)
datasets: [
{
label: intl.formatMessage({ id: `${messages}.etichetta` }), // Legenda tradotta
data: [], // Valori sull'asse Y (altezza delle barre)
backgroundColor: [] // Colore di ogni barra
}
]
};

// Itera sui dati ricevuti e costruisce le liste
for (const i in dati) {
etichette.push(dati[i].nome); // Nome del lavoratore → asse X
valori.push(dati[i].totale); // Totale ore → altezza della barra

// (i + 1) % 7: quando "i" arriva a 7, torna a 0, ripetendo i colori in ciclo
nuoviDatiGrafico.datasets[0].backgroundColor.push(colori[(i + 1) % 7]);
}

nuoviDatiGrafico.labels = etichette;
nuoviDatiGrafico.datasets[0].data = valori;

setDatiGrafico(nuoviDatiGrafico); // Salva nello stato per attivare il re-render

}, [dati]); // Esegui di nuovo ogni volta che "dati" cambia

// Finché i dati non sono stati elaborati, non renderizzare nulla
if (datiGrafico == null) {
return null;
}

return (
<Bar data={datiGrafico} />
);
}

export default GraficoBarre;

Capire il codice:

Questo componente ha tre responsabilità principali:

Preparazione useState: Inizializza il componente con stato null per attendere l'elaborazione dei dati provenienti dal server.

Elaborazione dei Dati useEffect: Quando i dati arrivano tramite la prop dati, l'Hook useEffect viene attivato automaticamente. Definisce una palette di colori e usa un ciclo for per separare le informazioni: i nomi dei lavoratori vanno nelle etichette dell'asse X labels, i totali delle ore definiscono l'altezza delle barre data, e un colore viene assegnato a ogni barra in modo ciclico.

Disegno sullo Schermo return: Utilizza il componente <Bar> della libreria per disegnare il grafico in base ai dati elaborati e salvati nello stato.

File index.less: Crea anche il file index.less dentro la cartella GraficoBarre. Per ora può rimanere vuoto.

Passo 6: Layout Visivo VisualizzazioneDati​

Ora che i componenti Tabella e Grafico sono pronti, abbiamo bisogno di un container che li posizioni fianco a fianco sullo schermo.

Vai alla cartella VisualizzazioneDati e crea un file chiamato index.jsx:

/apps/nome_della_tua_app/ui/src/containers/VisualizzazioneDati/index.jsx

Apri il file e incolla il codice seguente:

import React from 'react';

import { FormattedMessage } from 'react-intl'; // Titoli traducibili per lingua

import GraficoBarre from "../../components/GraficoBarre";
import ElencoTabella from "../../components/ElencoTabella";

import './index.less';

const messages = 'dashboardcontainer.visualizzazionedati';

function VisualizzazioneDati({ dati = [] }) {
return (
<div>
<div className="row"> {/* Riga di Bootstrap */}

{/* Colonna sinistra: Tabella */}
<div className="col-lg-6">
<div className="panel panel-default">
<div className="panel-heading visualizzazione-dati__pannello">
<h3 className="panel-title">
<FormattedMessage id={`${messages}.tabella.titolo`} />
</h3>
</div>
<div className="panel-body">
<ElencoTabella dati={dati} /> {/* Passa i dati alla Tabella */}
</div>
</div>
</div>

{/* Colonna destra: Grafico */}
<div className="col-lg-6">
<div className="panel panel-default">
<div className="panel-heading visualizzazione-dati__pannello">
<h3 className="panel-title">
<FormattedMessage id={`${messages}.grafico.titolo`} />
</h3>
</div>
<div className="panel-body">
<GraficoBarre dati={dati} /> {/* Passa gli stessi dati al Grafico */}
</div>
</div>
</div>

</div>
</div>
);
}

export default VisualizzazioneDati;

Capire il codice:

Questo componente agisce come un organizzatore visivo. Riceve la lista dati e la distribuisce ai due componenti figli senza alcuna trasformazione.

Le classi row e col-lg-6 sono del "Bootstrap Grid" e dividono lo schermo a metà: la colonna di sinistra riceve la Tabella e quella di destra riceve il Grafico. Su schermi più piccoli (cellulari), si impilano automaticamente.

Le classi panel, panel-heading e panel-body creano l'aspetto di una "scatolina con titolo" per ogni componente.

File index.less: Crea anche il file index.less in questa cartella per la futura stilizzazione dei pannelli.

Passo 7: Container Dashboard​

Abbiamo già i servizi nel backend e i componenti visivi nel frontend. Avremo bisogno di un container per andare al server, recuperare i dati e passarli al componente VisualizzazioneDati.

Netuno genera già automaticamente la struttura di base della Dashboard. Modifica il file esistente:

/apps/nome_della_tua_app/ui/src/containers/DashboardContainer/index.jsx

Sostituisci il contenuto del file con il codice seguente:

import React, { useState, useEffect, useImperativeHandle } from "react";

import _service from '@netuno/service-client'; // Client HTTP ufficiale di Netuno

import { Spin, message } from 'antd'; // Indicatore di caricamento ed errori

import VisualizzazioneDati from "../VisualizzazioneDati"; // Il layout visivo

import { useIntl } from 'react-intl'; // Supporto multilingua

import './index.less';

const messages = 'dashboardcontainer.visualizzazionedati';

function DashboardContainer({}, ref) {
const intl = useIntl();
const [lavoratori, setLavoratori] = useState([]); // Stato: elenco dei lavoratori
const [caricamento, setCaricamento] = useState(false); // Stato: sta caricando?

// All'apertura della pagina, avvia automaticamente la ricerca
useEffect(() => {
caricaLavoratori();
}, []);

const caricaLavoratori = () => {
setLavoratori([]); // Pulisci i dati precedenti
setCaricamento(true); // Attiva l'indicatore di caricamento

_service({
// Rileva la lingua del pannello e chiama l'URL corretto del servizio
url: intl.locale.indexOf('it') == 0 ? '/services/lavoratori' : '/services/workers',
success: (risposta) => {
setLavoratori(risposta.json); // Salva i dati ricevuti nello stato
setCaricamento(false);
},
fail: (e) => {
setCaricamento(false);
console.error('Servizio lavoratori non riuscito.', e);
message.error(intl.formatMessage({ id: `${messages}.errore_caricamento` }));
}
});
};

// Consente al componente padre (Dashboard) di avviare caricaLavoratori() tramite ref
useImperativeHandle(ref, () => ({
caricaLavoratori
}));

return (
<div ref={ref}>
{ caricamento == false
? <VisualizzazioneDati dati={lavoratori} /> // Dati pronti: mostra il pannello
: <Spin/> // Ancora in caricamento: mostra la rotella
}
</div>
);
}

export default React.forwardRef(DashboardContainer);

Capire il codice:

Questo componente ha tre responsabilità principali:

Stato iniziale useState: Crea due controlli interni — lavoratori (la lista proveniente dal server) e caricamento (un indicatore che segnala se i dati sono ancora in fase di recupero).

Recupero dati _service: La funzione caricaLavoratori usa _service per chiamare il servizio backend. A differenza di fetch nativo di JavaScript, _service gestisce autenticazione, sessione e gestione degli errori automaticamente. Rileva inoltre la lingua del pannello e sceglie l'URL corretto /lavoratori per IT, /workers per EN.

Distribuzione e Disegno return: Mentre i dati vengono recuperati, mostra la rotella di caricamento (Spin). Quando i dati arrivano, passa la lista lavoratori al componente VisualizzazioneDati, che la distribuisce alla Tabella e al Grafico.

Stilizzare la Dashboard​

Apri il file index.less della cartella DashboardContainer e incolla il codice seguente per evidenziare le intestazioni dei pannelli:

/apps/nome_della_tua_app/ui/src/containers/DashboardContainer/index.less
.sfondoPannello {
background-color: #3f54ba;
padding: 10px 15px;
color: white;
}

Capire il codice:

Questa classe definisce un colore di sfondo blu (#3f54ba) e un'area di riempimento (padding) nelle intestazioni.

Viene utilizzata dalle classi visualizzazione-dati__pannello del componente VisualizzazioneDati per garantire che i titoli Tabella e Grafico risultino visivamente evidenziati.

Passo 8: Compilare il Progetto​

Per garantire che tutto il codice React venga compilato da Netuno:

  • Se il tuo terminale è rimasto aperto con npm run watch in esecuzione in background: le tue modifiche vengono compilate automaticamente ogni volta che un file viene salvato.
  • Se hai chiuso il terminale o il processo si è fermato: nessun problema. Apri di nuovo il terminale, vai alla cartella ui ed esegui nuovamente il comando:
npm run watch

Conclusione​

Nel corso di questo tutorial, hai acquisito competenze essenziali per lavorare con la piattaforma Netuno, padroneggiando il ciclo di integrazione del flusso di dati tra il server e l'interfaccia utente.

Questo sviluppo ha trattato i seguenti argomenti:

  • Database: Creazione e organizzazione di tabelle e strutture dati in modo visivo ed efficiente.

  • Backend REST: Sviluppo di servizi e logica di business per rendere disponibili i dati via API.

  • Frontend con React: Integrazione e visualizzazione dinamica di questi dati in un'interfaccia moderna e responsiva.

Se hai difficoltà o suggerimenti di miglioramento, contattaci.

Il nostro team e la nostra community sono pronti ad aiutarti.

Buon sviluppo!