Overview
Timeline / Gantt
Archetype business per pianificazione operativa su asse temporale (gantt) metadata-driven: task come barre, milestone, dipendenze, drag/resize con persistenza. Rendering basato su frappe-gantt (SVG, lazy-loaded).
Use cases
- Pianificazione commesse/attività con date inizio/fine.
- Roadmap di progetto con milestone e dipendenze tra task.
- Ripianificazione rapida via drag (sposta) e resize (allunga/accorcia) delle barre.
md_props_bag: archetypes.timeline
L'archetype si attiva con timeline (alias accettati: gantt, gantt-list, timeline-list).
{
"archetypes": {
"timeline": {
"advancedFilter": false,
"startDateField": "StartDate",
"endDateField": "EndDate",
"titleField": "Title",advancedFilter: quandotruemostra lawuic-filter-barper l'archetypetimeline(a livello data-repeater).startDateField(obbligatorio): campo data di inizio del task. Se manca, il componente mostra un warning di configurazione e non crasha.endDateField: campo data di fine. Se il valore sul record è vuoto, il task è reso come milestone (barra di 1 giorno).titleField: campo testo usato come label della barra (fallback: prima colonna testuale conname/title/descrinel nome).progressField: campo numerico 0-100 per l'avanzamento (clampato; opzionale).dependencyField: campo con i predecessori. Sono accettate due forme (vedi "Predecessori: csv o multiselect" più sotto): una colonna testo con csv di id ("3, 7") oppure una colonnamultiple_check(il multiselect del framework). v1: le dipendenze sono rese come frecce standard fine→inizio (FS); se un task inizia prima della fine del predecessore viene mostrato un warning (non bloccante).milestoneField: campo booleano che marca il record come milestone (diamante, non ridimensionabile).groupByField: raggruppamento opzionale. v1: ordina i task per gruppo e prefissa il titolo con[gruppo](frappe-gantt non ha corsie per gruppo). Può puntare a una colonna testo o a unlookupByID— vedi "Raggruppamento: testo o lookup".persistMode(enum):
- immediate (default): ogni drag/resize persiste subito lato server (rollback visivo in caso di errore);
- batch: accumula le modifiche e salva/annulla in blocco dalla toolbar.
timeScale(enum):day,week(default),month— zoom iniziale, cambiabile dalla toolbar.showTodayLine: evidenzia il giorno corrente (defaulttrue).clickAction(enum):none(default),detail,edit— navigazione al click sulla barra.
Predecessori: csv o multiselect
dependencyField accetta due configurazioni, e il componente riconosce da solo quale è in uso:
1. Colonna testo con csv di id ("3, 7"): configurazione minima, nessuna tabella aggiuntiva. L'utente digita gli id a mano, quindi non c'è validazione né autocompletamento.
2. Colonna `multiple_check` — il multiselect del framework (vedi Widget Many To Many): è la forma consigliata, perché in edit l'utente sceglie i predecessori da un elenco di task invece di scrivere id.
Per la seconda serve il corredo standard del widget many-to-many, con una particolarità: la relazione è auto-referenziale, perché i predecessori di un task sono a loro volta task.
- una tabella ponte con due FK verso la tabella dei task (es.
wuic_timeline_task_depsconFK_TaskeFK_Predecessor); - sulla route principale una colonna virtuale (
mc_real_column_namevuoto,mc_is_computed = 1,voa_class = 4) di tipomultiple_check, conmc_ui_grid_routeche punta alla route stessa emc_ui_grid_manytomany_*che mappano le due FK della ponte; - sulla tabella ponte, entrambe le FK configurate come
lookupByIDverso la route dei task (voa_class = 2).
Quest'ultimo punto è il più facile da sbagliare: lo scaffolding crea le FK intere come number, ma il backend, quando compone i valori many-to-many, converte la colonna correlata a lookup e ne legge l'entità puntata. Se le FK restano number, ogni lettura della route padre fallisce con un NullReferenceException in ParseGridColumns.
In lettura il framework espone la colonna come collezione di record correlati: il componente estrae l'id di ciascun predecessore dal campo dichiarato in mc_ui_grid_related_id_field, con fallback sulla chiave primaria della route.
Lo script scripts/timeline-sample/scaffold-timeline-sample.ps1 dell'app applica esattamente questo schema su mssql, mysql, postgres e oracle ed è un riferimento pratico riutilizzabile.
Raggruppamento: testo o lookup
Come per i predecessori, groupByField accetta due configurazioni e il componente riconosce da sola quale è in uso:
1. Colonna testo: nessuna tabella aggiuntiva, ma il gruppo è digitato a mano — un refuso crea un gruppo nuovo, che compare come riga separata e, se il nome precede alfabeticamente gli altri, finisce in cima al grafico.
2. Colonna `lookupByID` verso un'anagrafica dedicata (es. wuic_timeline_project): è la forma consigliata, perché i gruppi diventano un insieme chiuso scelto da elenco.
Con il lookup il valore grezzo della colonna è l'id, non il nome: mostrarlo darebbe [3] Titolo. Il componente risolve l'etichetta con MetadatiColonna.formatGridViewValue, lo stesso risolutore usato dalla grid, che conosce l'alias denormalizzato emesso dal server (<route>___<textField>__<colonna>) e ricade su __lookup_obj quando il record non ha ancora fatto round-trip — così l'etichetta è corretta anche subito dopo un inserimento.
Ordinamento. Le righe sono ordinate per gruppo e, a parità di gruppo, per data di inizio: l'ordine non è topologico, quindi un task può comparire sopra il proprio predecessore se appartiene a un gruppo che lo precede alfabeticamente.
Regole di rendering dei dati
- Record senza
startDateFieldvalido → escluso dal gantt, conteggiato nel warning "righe escluse". endDateFieldvuoto → milestone implicita (end = start).end < start(nel dato o dopo un drag) → clamp astart+ warning.- La persistenza usa il flusso current-record del datasource (
syncDatacon pristine) → il payload__changescontiene solo le date modificate ed è compatibile con concorrenza ottimistica e audit.
Popup della barra e azioni sul record
Cliccando una barra si apre il popup con titolo, intervallo di date e avanzamento. Le azioni disponibili sotto al testo seguono i permessi dichiarati nei metadata della tabella, così una route in sola lettura mostra un popup puramente informativo:
| Azione | Condizione | Comportamento |
|---|---|---|
| Modifica | md_editable | Apre il record nel parametric-dialog, lo stesso form dell'edit da list-grid. |
| Nuovo | md_insertable | Apre il dialog di inserimento con il task cliccato già preselezionato fra i predecessori del nuovo task. |
| Elimina | md_deletable e task foglia | Chiede conferma ed elimina il record. |
Alla chiusura con salvataggio (o dopo l'eliminazione) il datasource ricarica e il gantt si ridisegna.
Perché solo le foglie si eliminano. Un task è foglia quando nessun altro task lo elenca fra i propri predecessori. Eliminare un task da cui altri dipendono lascerebbe dipendenze orfane — frecce verso un id che non esiste più — quindi su quei task il pulsante non compare affatto. Nell'esempio Analisi requisiti → Design architettura, il pulsante appare su Design architettura ma non su Analisi requisiti.
Preselezione del predecessore. Con dependencyField su colonna testo viene scritto direttamente l'id nel csv. Con la multiselect vengono valorizzati sia l'elenco di id sia la collezione di record correlati, marcando la voce come aggiunta: è lo stesso contratto che il lookup-editor produce quando l'utente sceglie a mano, ed è ciò che il backend usa per scrivere nella tabella ponte.
Il dialog viene caricato con un import dinamico al primo click, così il chunk dell'archetype non si porta dietro i field editor.
Eventi e subscriptions (host)
Eventi disponibili su wuic-timeline-list:
onTimelineDataBound: emesso quando il gantt viene ricostruito dal datasource ({ metaInfo, tasks, skipped }).onTimelineTaskChange: emesso su drag/resize ({ task, start, end, mode }).onTimelineTaskClick: emesso su click barra ({ task }).onTimelineBatchSave: emesso dopo salvataggio batch ({ savedCount }).onTimelineBatchCancel: emesso dopo annullamento batch ({ revertedCount }).
<!-- import-and-mount -->
Import e mount (componente Angular)
Importa LazyTimelineListComponent da 'wuic-framework-lib' e montalo col DataSource del framework:
import { DataSourceComponent, LazyTimelineListComponent } from 'wuic-framework-lib';
@Component({
selector: 'app-esempio',
imports: [DataSourceComponent, LazyTimelineListComponent],
template: `
<wuic-data-source #ds [hardcodedRoute]="'<route>'" [autoload]="true"></wuic-data-source>Nomi ESATTI (barrel wuic-framework-lib): classe LazyTimelineListComponent, selector <wuic-timeline-list-lazy>. Il componente non-lazy TimelineListComponent NON è esportato dal barrel (importa staticamente frappe-gantt): usa sempre il lazy wrapper.
Config metadata-driven: il tag accetta SOLO gli input standard [hardcodedRoute], [hardcodedDatasource], [hideToolbar]. La configurazione specifica (TimelineOptions: campi data, milestone, dipendenze, ecc.) NON è un input HTML → va nei metadata (md_props_bag.archetypes.timeline).