diff --git a/angular.json b/angular.json
index e87d2b0a1a6f02297dd35553c7d5cd37e50f7094..daa02deb9f77b04e2cb9ccf9839352466f7cdaf5 100644
--- a/angular.json
+++ b/angular.json
@@ -28,7 +28,9 @@
               "src/styles.scss",
               "src/theme.scss",
               "node_modules/material-design-icons/iconfont/material-icons.css",
-              "node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
+              "node_modules/roboto-fontface/css/roboto/roboto-fontface.css",
+              "node_modules/primeng/resources/primeng.min.css",
+              "node_modules/primeng/resources/themes/nova-light/theme.css"
             ],
             "scripts": [],
             "showCircularDependencies": false
diff --git a/jalhyd_branch b/jalhyd_branch
index 8b25206ff90e9432f6f1a8600f87a7bd695a24af..19c587308945308f3787a17aee4282c5c6de79df 100644
--- a/jalhyd_branch
+++ b/jalhyd_branch
@@ -1 +1 @@
-master
\ No newline at end of file
+33-ajout-du-calcul-d-une-passe-a-bassin
diff --git a/package-lock.json b/package-lock.json
index 265e3c560a30d324c6d0baf680706804d7c7a706..72d12202a4b58cec9399e6f57b455428be2edaf3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10859,6 +10859,11 @@
         "meow": "^3.1.0"
       }
     },
+    "primeng": {
+      "version": "8.0.0-rc.1",
+      "resolved": "https://registry.npmjs.org/primeng/-/primeng-8.0.0-rc.1.tgz",
+      "integrity": "sha512-xgynBphs4wtCbyyAVQD1ipQCVcSOlfZ697wVVIlZ76V9E1e6eYeNGXN/fLud3NngNyoFiJkP+VJL7kfcfWFnPQ=="
+    },
     "printj": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
diff --git a/package.json b/package.json
index bad7e6e723f7aecf7b0cef64641e728ad9467056..4d66e017eecbc9bb2e7625fe0f730789be4c973e 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,7 @@
     "ngx-md": "^7.0.0",
     "ngx-webstorage-service": "^4.0.1",
     "pako": "^1.0.10",
+    "primeng": "^8.0.0-rc.1",
     "roboto-fontface": "^0.10.0",
     "rxjs": "^6.3.3",
     "rxjs-compat": "^6.3.3",
@@ -103,4 +104,4 @@
       "android"
     ]
   }
-}
\ No newline at end of file
+}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 25698c7d554a3b7d5241a93965e23ea8e40f87ec..8b206ab68afcea020eb410182e518a5f91140273 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -26,6 +26,8 @@ import {
 } from "@angular/material";
 import { MaterialFileInputModule } from "ngx-material-file-input";
 
+import {TableModule} from "primeng/components/table/table";
+
 import { FlexLayoutModule } from "@angular/flex-layout";
 import {
   CustomBreakPointsProvider,
@@ -140,7 +142,8 @@ const appRoutes: Routes = [
         enableTracing: false // debugging purposes only
       }
     ),
-    StorageServiceModule
+    StorageServiceModule,
+    TableModule
   ],
   declarations: [ // composants, pipes et directives
     AppComponent,
diff --git a/src/app/components/pab-table/pab-table.component.html b/src/app/components/pab-table/pab-table.component.html
index 7eba1ea01643f72676ee76be88e3603dfdc70ac8..936d5eaeed6248b487bd798906e391e2e3205c6f 100644
--- a/src/app/components/pab-table/pab-table.component.html
+++ b/src/app/components/pab-table/pab-table.component.html
@@ -5,7 +5,54 @@
 </mat-card-header>
 
 <mat-card-content>
-    Ici le tableau méga guedin !
+
+    <button (click)="zpouet()">CLIKZYVOIR</button>
+
+    <p-table [columns]="cols" [value]="rows" class="pab-data-table">
+
+        <ng-template pTemplate="header" let-columns>
+            <tr>
+                <th *ngFor="let h of headers"
+                  (click)="toggleSelection(h, $event)"
+                  [attr.rowspan]="h.rowspan ? h.rowspan : null" [attr.colspan]="h.colspan ? h.colspan : null"
+                  [class.selectable-cell]="isSelectable(h)" [class.selected-cell]="isSelected(h)">
+
+                    {{ h.title }}
+                </th>
+            </tr>
+            <tr>
+                <th *ngFor="let col of columns">{{ col.title }}</th>
+            </tr>
+        </ng-template>
+
+        <ng-template pTemplate="body" let-row>
+            <tr [class.selected-row]="isSelected(row)">
+                <td *ngFor="let cell of row.cells"
+                  (click)="toggleSelection(cell, $event)"
+                  [ngClass]="cell.class"
+                  [class.editable-cell]="hasModel(cell)" [class.readonly-cell]="! hasModel(cell)"
+                  [class.selectable-cell]="isSelectable(cell)" [class.selected-cell]="isSelected(cell) && ! isSelected(row)"
+                  [attr.rowspan]="rowSpan(cell)" [attr.colspan]="colSpan(cell)">
+
+                    <input matInput *ngIf="isNumberInput(cell)" type="text" [(ngModel)]="cell.model.singleValue">
+
+                    <mat-select *ngIf="isSelect(cell)" [(value)]="cell.model">
+                        <!-- <mat-option *ngFor="let opt of cell.options | keyvalue" [value]="l.key">
+                            {{ l.value }}
+                        </mat-option> -->
+                        <mat-option *ngFor="let opt of cell.options" [value]="opt">
+                            {{ opt }}
+                        </mat-option>
+                    </mat-select>
+
+                    <span *ngIf="! hasModel(cell)">{{ cellValue(cell) }}</span>
+                </td>
+            </tr>
+        </ng-template>
+    </p-table>
+
+    <!-- <pre>{{ rows | json }}</pre> -->
+
     <!-- <field-set *ngFor="let fs of fieldsets" class="fieldset-inner" [fieldSet]=fs
         (radio)=onRadioClick($event) (validChange)=onFieldsetValid() (inputChange)=onInputChange($event)
         (addFieldset)=onAddFieldset($event) (removeFieldset)=onRemoveFieldset($event)
diff --git a/src/app/components/pab-table/pab-table.component.scss b/src/app/components/pab-table/pab-table.component.scss
index fcc76e8963f179c1243149cc1e3a445045d251c9..05a284ae028eb49b574162af406fad93d3d80bd4 100644
--- a/src/app/components/pab-table/pab-table.component.scss
+++ b/src/app/components/pab-table/pab-table.component.scss
@@ -15,7 +15,7 @@ mat-card-header {
     // Pourquoi n'est-ce pas hérité de calculator.component.scss ?
     // À cause de la surcharge de mat-card-header ci-dessus ?
     mat-card-title {
-        font-size: 16px;
+        font-size: 16px !important;
         margin-bottom: 8px;
     }
 }
@@ -23,3 +23,93 @@ mat-card-header {
 mat-card-content {
     margin-top: 1em;
 }
+
+// cells colors
+
+@import "../../../theme.scss";
+
+.editable-cell-bg {
+    // background-color: #e9f5ff;
+    // background-color: yellow;
+    @extend .bg-accent-extralight;
+}
+.selected-row-bg {
+    // background-color: green;
+    // @extend .bg-warn-extralight;
+    @extend .bg-warn-extralight;
+}
+.selected-cell-bg {
+    // background-color: red;
+    // @extend .bg-warn-extralight;
+    @extend .bg-warn-extralight;
+}
+.selected-editable-cell-bg {
+    // background-color: red;
+    @extend .bg-accent-verylight;
+}
+
+.pab-data-table {
+    ::ng-deep .ui-table.ui-widget {
+        table {
+            thead.ui-table-thead {
+                > tr {
+                    > th {
+                        font-size: .8em;
+                        padding: 6px 8px;
+                        &.selectable-cell {
+                            cursor: pointer;
+                        }
+                        &.selected-cell {
+                            @extend .selected-cell-bg;
+                        }
+                    }
+                }
+            }
+            tbody.ui-table-tbody {
+                > tr {
+                    &.selected-row {
+                        @extend .selected-row-bg;
+                        > td {
+                            &.editable-cell {
+                                @extend .selected-editable-cell-bg;
+                            }
+                        }
+                    }
+                    > td {
+                        &:nth-child(1) {
+                            width: 50px;
+                        }
+                        font-size: .8em;
+                        &.readonly-cell {
+                            cursor: default;
+                            padding: 4px 8px;
+                        }
+                        &.editable-cell {
+                            @extend .editable-cell-bg;
+                            padding: 0;
+                            > input.mat-input-element {
+                                padding: 4px 8px;
+                            }
+                        }
+                        &.selectable-cell {
+                            cursor: pointer;
+                        }
+                        &.selected-cell {
+                            @extend .selected-cell-bg;
+                            &.editable-cell {
+                                @extend .selected-editable-cell-bg;
+                            }
+                        }
+                        &.basin_number {
+                            text-align: center;
+                            font-weight: bold;
+                        }
+                        > mat-select.mat-select {
+                            padding: 0 8px;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/components/pab-table/pab-table.component.ts b/src/app/components/pab-table/pab-table.component.ts
index 695c33fc3cd3f0de55d662ae197c6d580f5b959e..0364c44b810bb7a57adc31496d28a3a18832af77 100644
--- a/src/app/components/pab-table/pab-table.component.ts
+++ b/src/app/components/pab-table/pab-table.component.ts
@@ -1,6 +1,7 @@
-import { Component, Input, Output, EventEmitter, QueryList, ViewChildren, DoCheck, AfterViewInit } from "@angular/core";
+import { Component, Input, Output, EventEmitter, DoCheck, AfterViewInit, OnInit } from "@angular/core";
+
+import { Pab, Session, PabCloisons, Props, CalculatorType, ParallelStructure, LoiDebit, StructureProperties, Cloisons, Nub } from "jalhyd";
 
-import { FieldSetComponent } from "../field-set/field-set.component";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
 import { PabTable } from "../../formulaire/pab-table";
 
@@ -14,48 +15,45 @@ import { PabTable } from "../../formulaire/pab-table";
         "./pab-table.component.scss"
     ]
 })
-export class PabTableComponent implements DoCheck, AfterViewInit {
+export class PabTableComponent implements /* DoCheck, AfterViewInit, */ OnInit {
 
     @Input()
     private pabTable: PabTable;
 
-    /**
-     * liste des composants FieldSet enfants
-     */
-    @ViewChildren(FieldSetComponent)
-    private _fieldsetComponents: QueryList<FieldSetComponent>;
-
-    /**
-     * flag de validité des FieldSet enfants
-     */
+    /** flag de validité des FieldSet enfants */
     private _isValid = false;
 
-    /**
-     * événément de changement d'état d'un radio
-     */
-    // tslint:disable-next-line:no-output-on-prefix
-    @Output()
-    private radio = new EventEmitter<any>();
-
-    /**
-     * événément de changement de validité
-     */
+    /** événément de changement de validité */
     @Output()
     private validChange = new EventEmitter();
 
-    /**
-     * événément de changement de valeur d'un input
-     */
+    /** événément de changement de valeur d'un input */
     @Output()
     private inputChange = new EventEmitter();
 
-    /** événement signalant un appui sur TAB ou SHIFT+TAB */
-    @Output()
-    protected tabPressed = new EventEmitter<any>();
+    /** underlying Pab, binded to the rows */
+    private model: Pab;
+
+    /** general headers above the columns */
+    public headers: any[];
+
+    /** columns headers description */
+    public cols: any[];
+
+    /** data binded to the table */
+    public rows: any[];
+
+    /** items currently selected */
+    private selectedItems: any[];
+
+    /** used for shift+click implementation */
+    private latestClickedCell: any;
 
     public constructor(
         private i18nService: I18nService
-    ) { }
+    ) {
+        this.selectedItems = [];
+    }
 
     public get title(): string {
         return this.i18nService.localizeText("INFO_PAB_TABLE");
@@ -65,39 +63,570 @@ export class PabTableComponent implements DoCheck, AfterViewInit {
         return this._isValid;
     }
 
+    /** returns true if the cell has an underlying model (ie. is editable) */
+    public hasModel(cell: any): boolean {
+        return (cell !== undefined && cell.model !== undefined);
+    }
+
+    /** returns true if the cell is an editable number */
+    public isNumberInput(cell: any): boolean {
+        return this.hasModel(cell) && ! this.isSelect(cell);
+    }
+
+    /** returns true if the cell is a select box @TODO rename */
+    public isSelect(cell: any): boolean {
+        return this.hasModel(cell) && (cell.options !== undefined);
+    }
+
+    /** value to display in a cell, if it is not editable */
+    public cellValue(cell: any) {
+        if (cell === undefined) {
+            return "";
+        } else {
+            if (this.hasModel(cell)) {
+                return cell.model;
+            } else {
+                return cell.value;
+            }
+        }
+    }
+
+    public rowSpan(cell: any) {
+        if (cell !== undefined && cell.rowspan) {
+            return cell.rowspan;
+        }
+        return undefined;
+    }
+
+    public colSpan(cell: any) {
+        if (cell !== undefined && cell.colspan) {
+            return cell.colspan;
+        }
+        return undefined;
+    }
+
+    /** returns true if the cell / row has a selectable item */
+    public isSelectable(cellOrRow: any): boolean {
+        return (
+            cellOrRow !== undefined
+            && cellOrRow.selectable
+        );
+    }
+
+    /** returns true if the cell / row has a selectableColumn item */
+    public isSelectableByColumn(cellOrRow: any): boolean {
+        return (
+            cellOrRow !== undefined
+            && cellOrRow.selectableColumn
+        );
+    }
+
     /**
-     * Ajoute un nouveau sous-nub (Structure, PabCloisons selon le cas)
-     * dans un nouveau fieldset
+     *  - checks if the cell / row has a selectable item, that is currently
+     *      selected
+     *  - if cell / row has a selectableColumn attribute, also checks if
+     *      this column is selected
+     *
+     *  returns true if at least one criterion is met
      */
-    /* private addSubNub(after?: FieldSet, clone: boolean = false) {
-        if (after) {
-            const newFs = this._container.addFromTemplate(0, after.indexAsKid());
-            if (clone) {
-                // replace in-place to change properties (overkill)
-                newFs.setPropValue("structureType", after.properties.getPropValue("structureType"));
-                newFs.setPropValue("loiDebit", after.properties.getPropValue("loiDebit"));
-                // after.nub.properties
-                for (const p of after.nub.prms) {
-                    newFs.nub.getParameter(p.symbol).singleValue = p.singleValue;
+    public isSelected(cellOrRow: any): boolean {
+        let cellSelected = false;
+        let columnSelected = false;
+        // cell
+        if (this.isSelectable(cellOrRow)) {
+            cellSelected = true;
+            if (Array.isArray(cellOrRow.selectable)) {
+                for (const elt of cellOrRow.selectable) {
+                    cellSelected = cellSelected && this.selectedItems.includes(elt);
                 }
+            } else {
+                cellSelected = this.selectedItems.includes(cellOrRow.selectable);
             }
-        } else {
-            this._container.addFromTemplate(0);
         }
-    } */
+        // column
+        if (this.isSelectableByColumn(cellOrRow)) {
+            columnSelected = this.isDeviceColumnSelected(cellOrRow.selectableColumn);
+        }
+        // done
+        return (cellSelected || columnSelected);
+    }
+
+    /**
+     * returns true if every wall (including downwall) has its nth device
+     * selected (or has no nth device)
+     */
+    public isDeviceColumnSelected(n: number): boolean {
+        let ok = true;
+        for (const c of this.model.children) {
+            const nthChild = c.getChildren()[n];
+            if (nthChild) {
+                ok = ok && this.selectedItems.includes(nthChild);
+            }
+        }
+        const nthChildDW = this.model.downWall.getChildren()[n];
+        if (nthChildDW) {
+            ok = ok && this.selectedItems.includes(nthChildDW);
+        }
+        return ok;
+    }
+
+    /**
+     * selects or unselects the clicked cell, depending on its current state
+     * and the modifier key held if any
+     */
+    public toggleSelection(cell: any, $event: any) {
+        if (
+            this.isSelectable(cell)
+            && ! this.hasModel(cell) // editable cells listen to the click event for edition only
+        ) {
+            if ($event.shiftKey && cell !== this.latestClickedCell) { // shift + click
+                // @TODO interpopolate from this.latestClickedCell to this one
+                console.log("shift + click");
+
+            } else if (
+                $event.ctrlKey // ctrl + click
+                || ($event.shiftKey && cell === this.latestClickedCell) // shift on same cell => equiv. of ctrl
+            ) {
+                if (this.isSelected(cell)) {
+                    // unselect this cell / these cells
+                    if (Array.isArray(cell.selectable)) {
+                        this.selectedItems = this.selectedItems.filter(e => ! cell.selectable.includes(e));
+                    } else {
+                        this.selectedItems = this.selectedItems.filter(e => e !== cell.selectable);
+                    }
+                } else {
+                    // add this cell / these cells to selection
+                    if (Array.isArray(cell.selectable)) {
+                        this.selectedItems = this.selectedItems.concat(cell.selectable);
+                        this.selectedItems = this.selectedItems.filter(
+                            (item, index) => this.selectedItems.indexOf(item) === index // deduplicate
+                        );
+                    } else {
+                        this.selectedItems.push(cell.selectable);
+                    }
+                }
+
+            } else { // just a click
+                if (this.isSelected(cell)) {
+                    // select nothing
+                    this.selectedItems = [];
+                } else {
+                    // select this cell / thses cells only
+                    if (Array.isArray(cell.selectable)) {
+                        this.selectedItems = cell.selectable.slice(); // array copy
+                    } else {
+                        this.selectedItems = [ cell.selectable ];
+                    }
+                }
+            }
+            this.latestClickedCell = cell;
+            // @TODO
+            /* $event.preventDefault();
+            $event.stopPropagation();
+            return false; */
+        }
+    }
 
-    public ngAfterViewInit() {
-        /* this.onFieldsetListChange();
-        this._fieldsetComponents.changes.subscribe(_ => this.onFieldsetListChange()); */
+    // DEBUG
+    private dumpParams(n: Nub) {
+        console.log(`---- PARAMETERS FOR NUB "${n.uid}" (${n.constructor.name}) ----`);
+        for (const p of n.parameterIterator) {
+            if (p.visible) {
+                console.log(`> ${p.symbol} : ${p.singleValue}, ${p.currentValue}, ${p.v}`);
+            }
+        }
+    }
+
+    public zpouet() {
+        console.log("!!!------------ zpoueeeeeeeeet ------------!!!");
+        for (const c of this.model.children) {
+            this.dumpParams(c);
+        }
+        this.dumpParams(this.model.downWall);
+    }
+
+    // at this time @Input data is supposed to be already populated
+    public ngOnInit() {
+        // get model
+        this.model = this.pabTable.pab;
+
+        // ------------- TEST ------------------------
+
+        // empty PAB
+        const chLen = this.model.children.length;
+        for (let i = 0; i < chLen; i++) {
+            this.model.deleteChild(0);
+        }
+
+        // cloison à 1 ouvrage
+        const cl1 = Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Cloisons })
+        ) as Cloisons;
+        cl1.addChild(Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.RectangularOrificeFree }), cl1
+        ));
+        // PabCloison associé
+        const pc1 = Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.PabCloisons }), this.model
+        ) as PabCloisons;
+        pc1.initModelCloisons(cl1);
+        pc1.prms.setCurrentValuesFromModel();
+        this.model.addChild(pc1);
+        this.dumpParams(pc1);
+
+        // cloison à 3 ouvrages
+        const cl2 = Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Cloisons })
+        ) as Cloisons;
+        cl2.addChild(Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.GateCem88v }), cl2
+        ));
+        cl2.addChild(Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.RectangularOrificeFree }), cl2
+        ));
+        cl2.addChild(Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.RectangularOrificeSubmerged }), cl2
+        ));
+        // PabCloison associé
+        const pc2 = Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.PabCloisons }), this.model
+        ) as PabCloisons;
+        pc2.initModelCloisons(cl2);
+        pc2.prms.setCurrentValuesFromModel();
+        this.model.addChild(pc2);
+        this.dumpParams(pc2);
+
+        // cloison à 2 ouvrages
+        const cl3 = Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Cloisons })
+        ) as Cloisons;
+        cl3.addChild(Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.GateCem88v }), cl3
+        ));
+        cl3.addChild(Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.RectangularOrificeFree }), cl3
+        ));
+        // PabCloison associé
+        const pc3 = Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.PabCloisons }), this.model
+        ) as PabCloisons;
+        pc3.initModelCloisons(cl3);
+        pc3.prms.setCurrentValuesFromModel();
+        this.model.addChild(pc3);
+        this.dumpParams(pc3);
+
+        // cloison aval à 1 ouvrage
+        const cloisonAval: ParallelStructure = Session.getInstance().createSessionNub(
+            new Props({ calcType: CalculatorType.ParallelStructure })
+        ) as ParallelStructure;
+        cloisonAval.addChild(Session.getInstance().createNub(
+            new Props({ calcType: CalculatorType.Structure, loiDebit: LoiDebit.KIVI }), cloisonAval
+        ));
+        this.model.setDownWall(cloisonAval.uid);
+        this.dumpParams(this.model.downWall);
+
+        // debug
+        console.log("Model in ngOnInit:", this.model);
+
+        // ------------- FIN TEST --------------------
+
+        this.buildTable();
+    }
+
+    /**
+     * Builds the editable data grid from the Pab model
+     */
+    private buildTable() {
+        const maxNbDevices = this.findMaxNumberOfDevices();
+
+        // 0. build spanned headers over real columns
+        this.headers = [];
+        // 1 header for basin
+        this.headers.push({
+            title: "Bassin",
+            colspan: 6
+        });
+        // 1 header for each device of the wall having the most devices (including downwall)
+        for (let i = 0; i < maxNbDevices; i++) {
+            this.headers.push({
+                title: "Cloison : ouvrage n°" + (i + 1),
+                colspan: 3,
+                selectable: this.model.children.map(c => c.getChildren()[i]).concat(this.model.downWall.getChildren()[i]),
+                selectableColumn: i
+            });
+        }
+
+        // A. build columns set
+        this.cols = [];
+        // 6 cols for basin
+        this.cols.push({
+            title: "N° de bassin"
+        });
+        this.cols.push({
+            title: "Longueur"
+        });
+        this.cols.push({
+            title: "Largeur"
+        });
+        this.cols.push({
+            title: "Débit d'attrait"
+        });
+        this.cols.push({
+            title: "Cote radier mi-bassin"
+        });
+        this.cols.push({
+            title: "Cote radier amont paroi"
+        });
+        // no col for wall type (defined by rowspan-2 header above)
+        // 3 cols for each device of the wall having the most devices (including downwall)
+        for (let i = 0; i < maxNbDevices; i++) {
+            this.cols.push({
+                title: "Type"
+            });
+            this.cols.push({
+                title: "Paramètres"
+            });
+            this.cols.push({
+                title: "Valeurs"
+            });
+        }
+
+        // B. Build rows set
+        this.rows = [];
+        // B.1 many rows for each wall
+        let childIndex = 0;
+        for (const cloison of this.model.children) {
+            // as much rows as the greatest number of parameters among its devices
+            const maxNbParams = this.findMaxNumberOfDeviceParameters(cloison);
+            // console.log(">>> max nb params: ", maxNbParams);
+            for (let i = 0; i < maxNbParams; i++) {
+                // build device params row
+                const deviceParamRow = { selectable: cloison, cells: [] };
+                // basin number
+                if (i === 0) {
+                    deviceParamRow.cells.push({
+                        value: childIndex + 1,
+                        rowspan: maxNbParams + 1,
+                        class: "basin_number",
+                        selectable: cloison
+                    });
+                }
+                // 5 empty cells
+                if (i === 0) {
+                    deviceParamRow.cells.push({
+                        colspan: 5,
+                        rowspan: maxNbParams,
+                        selectable: cloison
+                    });
+                }
+                // device param cells : 3 cells for each device
+                for (const ouvrage of cloison.structures) {
+                    const nvParam = ouvrage.getNthVisibleParam(i);
+                    // cell 1 : device type
+                    if (i === 0) { // 1st row
+                        deviceParamRow.cells.push({
+                            model: ouvrage.properties.getPropValue("loiDebit"),
+                            // options: StructureProperties.findCompatibleLoiDebit(ouvrage)
+                            options: [ "salut", "coucou", "pouet" ],
+                            selectable: ouvrage
+                        });
+                    }
+                    // fill space
+                    if (i === 1) {
+                        deviceParamRow.cells.push({
+                            rowspan: (maxNbParams - 1),
+                            selectable: ouvrage
+                        });
+                    }
+                    // cell 2 : param name
+                    if (nvParam) {
+                        deviceParamRow.cells.push({
+                            value: nvParam.symbol,
+                            selectable: ouvrage
+                        });
+                    } else {
+                        deviceParamRow.cells.push({
+                            selectable: ouvrage
+                        });
+                    }
+                    // cell 3 : param value
+                    if (nvParam) {
+                        deviceParamRow.cells.push({
+                            model: nvParam,
+                            selectable: ouvrage
+                        });
+                    } else {
+                        deviceParamRow.cells.push({
+                            selectable: ouvrage
+                        });
+                    }
+                }
+                // fill horizontal space
+                const devDiff =  (maxNbDevices - cloison.structures.length);
+                if (i === 0) {
+                    for (let j = 0; j < devDiff; j++) {
+                        deviceParamRow.cells.push({
+                            colspan: 3,
+                            rowspan: maxNbParams,
+                            selectable: cloison,
+                            selectableColumn: cloison.structures.length + j
+                        });
+                    }
+                }
+                // done !
+                this.rows.push(deviceParamRow);
+            }
+            // 1 row for the basin after the wall
+            const basinRow: { selectable: any, cells: any[] } = {
+                selectable: cloison,
+                cells: [
+                    // no cell for basin number (defined by rowspan-n cell above)
+                    {
+                        model: cloison.prms.LB
+                    },
+                    {
+                        model: cloison.prms.BB
+                    },
+                    {
+                        model: cloison.prms.QA
+                    },
+                    {
+                        model: cloison.prms.ZRMB
+                    },
+                    {
+                        model: cloison.prms.ZRAM
+                    }
+                ]
+            };
+            // fill horizontal space
+            for (let i = 0; i < maxNbDevices; i++) {
+                basinRow.cells.push({
+                    colspan: 3
+                });
+            }
+            // done !
+            this.rows.push(basinRow);
+            childIndex ++;
+        }
+
+        // B.2 many rows for downwall
+        // as much rows as the greatest number of parameters among its devices
+        const maxNbParamsDW = this.findMaxNumberOfDeviceParameters(this.model.downWall);
+        for (let i = 0; i < maxNbParamsDW; i++) {
+            // build device params row
+            const deviceParamRowDW = { selectable: this.model.downWall, cells: [] };
+            // basin number
+            if (i === 0) {
+                deviceParamRowDW.cells.push({
+                    value: "Aval",
+                    rowspan: maxNbParamsDW,
+                    class: "basin_number",
+                    selectable: this.model.downWall
+                });
+            }
+            // 5 empty cells
+            if (i === 0) {
+                deviceParamRowDW.cells.push({
+                    colspan: 5,
+                    rowspan: maxNbParamsDW,
+                    selectable: this.model.downWall
+                });
+            }
+            // downwall device param cells : 3 cells for each device
+            for (const ouvrage of this.model.downWall.structures) {
+                const nvParam = ouvrage.getNthVisibleParam(i);
+                // cell 1 : device type
+                if (i === 0) { // 1st row
+                    deviceParamRowDW.cells.push({
+                        model: ouvrage.properties.getPropValue("loiDebit"),
+                        // options: StructureProperties.findCompatibleLoiDebit(ouvrage)
+                        options: [ "salut", "coucou", "pouet" ]
+                    });
+                }
+                // fill space
+                if (i === 1) {
+                    deviceParamRowDW.cells.push({
+                        rowspan: (maxNbParamsDW - 1),
+                        selectable: ouvrage
+                    });
+                }
+                // cell 2 : param name
+                if (nvParam) {
+                    deviceParamRowDW.cells.push({
+                        value: nvParam.symbol,
+                        selectable: ouvrage
+                    });
+                } else {
+                    deviceParamRowDW.cells.push({
+                        selectable: ouvrage
+                    });
+                }
+                // cell 3 : param value
+                if (nvParam) {
+                    deviceParamRowDW.cells.push({
+                        model: nvParam,
+                        selectable: ouvrage
+                    });
+                } else {
+                    deviceParamRowDW.cells.push({
+                        selectable: ouvrage
+                    });
+                }
+            }
+            // fill horizontal space
+            const devDiff =  (maxNbDevices - this.model.downWall.structures.length);
+            if (i === 0) {
+                for (let j = 0; j < devDiff; j++) {
+                    deviceParamRowDW.cells.push({
+                        colspan: 3,
+                        rowspan: maxNbParamsDW,
+                        selectable: this.model.downWall,
+                        selectableColumn: this.model.downWall.structures.length + j
+                    });
+                }
+            }
+            // done !
+            this.rows.push(deviceParamRowDW);
+        }
+    }
+
+    private findMaxNumberOfDevices(): number {
+        let maxNbDevices = 1;
+        for (const w of this.model.children) {
+            maxNbDevices = Math.max(maxNbDevices, w.getChildren().length);
+        }
+        maxNbDevices = Math.max(maxNbDevices, this.model.downWall.getChildren().length);
+        return maxNbDevices;
+    }
+
+    private findMaxNumberOfDeviceParameters(struct: ParallelStructure): number {
+        let maxNbParams = 1;
+        for (const d of struct.getChildren()) {
+            let nbParams = 0;
+            for (const p of d.parameterIterator) {
+                if (p.visible) {
+                    // console.log("(counting)", p.symbol);
+                    nbParams ++;
+                }
+            }
+            // console.log(">>> child params: ", nbParams);
+            maxNbParams = Math.max(maxNbParams, nbParams);
+        }
+        return maxNbParams;
+    }
+
+    /* public ngAfterViewInit() {
+        this.onFieldsetListChange();
+        this._fieldsetComponents.changes.subscribe(_ => this.onFieldsetListChange());
     }
 
     public ngDoCheck() {
         this.updateValidity();
-    }
+    } */
 
     /**
-    * calcul de la validité de tous les FieldSet de la vue
-    */
+     * @TODO Calcul de la validité de la Pab
+     */
     private updateValidity() {
         this._isValid = false;
 
@@ -130,20 +659,6 @@ export class PabTableComponent implements DoCheck, AfterViewInit {
         this.inputChange.emit($event);
     }
 
-    /**
-     * relit les valeurs dans l'interface et met à jour les NgParameter
-     */
-    public updateParametersFromUI() {
-        this._fieldsetComponents.forEach(fsc => fsc.updateParametersFromUI());
-    }
-
-    /**
-     * met à jour les paramètres liés
-     */
-    public updateLinkedParameters() {
-        this._fieldsetComponents.forEach(fsc => fsc.updateLinkedParameters());
-    }
-
     /**
      * Renvoie l'événement au composant du dessus
      */
diff --git a/src/app/formulaire/definition/concrete/form-pab.ts b/src/app/formulaire/definition/concrete/form-pab.ts
index c8dce277d616d4029dfd522596d07a53e1c9e37d..0ad94bf6673b8f267aa56dc9125910cea7473616 100644
--- a/src/app/formulaire/definition/concrete/form-pab.ts
+++ b/src/app/formulaire/definition/concrete/form-pab.ts
@@ -31,7 +31,7 @@ export class FormulairePab extends FormulaireBase {
         this._formCompute = new FormComputePab(this, (this._formResult as FormResultPab));
     }
 
-    private get pabNub(): Pab {
+    public get pabNub(): Pab {
         return this.currentNub as Pab;
     }
 
@@ -230,7 +230,7 @@ export class FormulairePab extends FormulaireBase {
     // debug method
     private dumpPabStructure(pab: Pab) {
         console.log(`PAB: ${pab.uid}, ${pab.children.length} children`);
-        /* for (const c of pab.children) {
+        for (const c of pab.children) {
             console.log(
                 ` * child: ${c.uid}, based on ${c.properties.getPropValue("modeleCloisons")}`
                 + ` (cote amont ${c.prms.Z1.singleValue}, longueur ${c.prms.LB.singleValue})`
@@ -238,6 +238,6 @@ export class FormulairePab extends FormulaireBase {
         }
         if (pab.downWall) {
             console.log(`+ downstream wall: ${pab.downWall.uid}`);
-        } */
+        }
     }
 }
diff --git a/src/app/formulaire/pab-table.ts b/src/app/formulaire/pab-table.ts
index 1591b4a188462d0c5fcffb6e8e42c55ab97bdce0..9464eeef8ba8baaacf454afd9e3eb201b5c3e103 100644
--- a/src/app/formulaire/pab-table.ts
+++ b/src/app/formulaire/pab-table.ts
@@ -1,8 +1,14 @@
+import { Pab } from "jalhyd";
+
 import { FormulaireElement } from "./formulaire-element";
 import { FormulaireNode } from "./formulaire-node";
+import { FormulairePab } from "./definition/concrete/form-pab";
 
 /**
- * The big editable data grid for calculator type "Pab" (model)
+ * The big editable data grid for calculator type "Pab" (form element).
+ *
+ * This is just a gateway between the model (FormPab and its underlying Pab)
+ * and the user interface (PabTableComponent)
  */
 export class PabTable extends FormulaireElement {
 
@@ -15,4 +21,20 @@ export class PabTable extends FormulaireElement {
     public parseConfig(json: {}) {
         this._confId = json["id"];
     }
+
+    /**
+     * Returns the parent FormulairePab
+     */
+    public get form(): FormulairePab {
+        return this.parentForm as FormulairePab;
+    }
+
+    /**
+     * Returns the Pab model associated to the parent form
+     */
+    public get pab(): Pab {
+        if (this.form) {
+            return this.form.pabNub;
+        }
+    }
 }
diff --git a/src/theme.scss b/src/theme.scss
index 7c38e7153a7ffc8bddc72f8fedaaa3bb948c16b2..f1704256b417d787226a4cb884504e6ec19c7ea2 100644
--- a/src/theme.scss
+++ b/src/theme.scss
@@ -183,12 +183,23 @@ $accent: map-get($nghyd-theme, accent);
 $warn: map-get($nghyd-theme, warn);
 
 // convenience classes (functions mat-* cannot be used outside of this file)
+
 .color-primary {
     color: mat-color($primary);
 }
 .bg-primary {
     background-color: mat-color($primary);
 }
+.bg-primary-light {
+    background-color: mat-color($primary, 300);
+}
+.bg-primary-verylight {
+    background-color: mat-color($primary, 100);
+}
+.bg-primary-extralight {
+    background-color: mat-color($primary, 50);
+}
+
 .color-accent {
     color: mat-color($accent);
 }
@@ -198,12 +209,28 @@ $warn: map-get($nghyd-theme, warn);
 .bg-accent-light {
     background-color: mat-color($accent, 300);
 }
+.bg-accent-verylight {
+    background-color: mat-color($accent, 100);
+}
+.bg-accent-extralight {
+    background-color: mat-color($accent, 50);
+}
+
 .color-warn {
     color: mat-color($warn);
 }
 .bg-warn {
     background-color: mat-color($warn);
 }
+.bg-warn-light {
+    background-color: mat-color($warn, 300);
+}
+.bg-warn-verylight {
+    background-color: mat-color($warn, 100);
+}
+.bg-warn-extralight {
+    background-color: mat-color($warn, 50);
+}
 
 .mat-button-toggle-checked {
     background-color: mat-color($accent);