Developer Spec Mockup · Pier Leg Color Logic — Any Row × Column (v0.4)
v0.4: Rows and Columns are now editable per pier below — try 3×3, 3×4, 4×3, 5×9, or any combination. Everything recomputes live.
pile_row_no drawPile() / drawPileFullView()
Why this matters: real pier data proves the pile grid is never a fixed size — one pier can be 12 piles across a row-count of 4, a sibling pier 9 piles across a row-count of 3. Column count must always come from that pier's own pile_row_no field, never a hardcoded number. The playground below lets you punch in any Rows × Columns and verify the leg/dot logic holds for all of them, not just the examples we picked.

The Rule

Number of legs = pile_row_no (Columns) for that specific pier/abutment — read from that pier's own data, never a fixed number. Each leg is colored independently, using only the piles that fall in its own column of the dot-grid below it — leg 1 ↔ column 1 dots, leg 2 ↔ column 2 dots, and so on, regardless of how many rows or columns exist. A column's own dots decide that column's leg only; it never affects any other leg.

Not started
0 of N in that column done
In progress — dark yellow (new)
1..N-1 of N in that column done
Complete
N of N in that column done

Playground — set any Rows × Columns per pier

Edit the Rows / Cols boxes on any card (or click a preset), then press Enter or click away — the pier's leg count, dot-grid, and colors all rebuild to match. Wide grids scroll horizontally within the card; legs, dots, and buttons stay column-aligned as one unit.

Where this goes in the code

No backend or data-model change — pileComplete[pierKey] and pile_row_no are already loaded client-side. Two things change: the color decision built before calling drawPile(), and drawPile()'s own loop (fixed count → array of colors). This logic works unchanged for any Rows × Columns — nothing here assumes a particular grid size.

// 1) Caller side — inside drawPier() / drawAbutment(), build one color PER COLUMN
const cols = parseInt(pierData.pile_row_no, 10) || 1;   // dynamic per pier, e.g. 3, 4, 5, 9...
const pileComplete = bridgeData.pileComplete[pierKey] || [];
const totalPiles = parseInt(pierData.pile_pier_no, 10) || 0;
const rows = Math.ceil(totalPiles / cols);

const legColors = [];
for (let c = 0; c < cols; c++) {
  const columnPiles = [];
  for (let row = 0; row < rows; row++) {
    const pileIndex = row * cols + c;                  // matches drawPileFullView()'s own indexing
    if (pileIndex < totalPiles) columnPiles.push('pile_' + (pileIndex + 1));
  }
  const doneInColumn = columnPiles.filter(p => pileComplete.includes(p)).length;

  if (doneInColumn === 0) {
    legColors.push('#fff9d9');            // not started (unchanged color)
  } else if (doneInColumn === columnPiles.length) {
    legColors.push('#7ED321');            // complete (unchanged color)
  } else {
    legColors.push('#D9A526');            // NEW: in progress
  }
}

// pass an ARRAY of colors, and cols (not a fixed number) as the bar count:
this.drawPile(x - pileWidth/2, pileY, pileWidth, pileHeight, legColors, id+'-pile-group', cols, pierData, pierKey);

// 2) drawPile() — now takes fillColors[] and draws `numLegs` bars, each its own color
drawPile(x, y, width, height, fillColors, id, numLegs = 0, _pileSource = {}, locationKey = null) {
    const pWidth = 6;
    const gap = numLegs > 1 ? (width - (numLegs * pWidth)) / (numLegs - 1) : 0;
    for (let i = 0; i < numLegs; i++) {
        const px = x + i * (pWidth + gap);
        const fill = Array.isArray(fillColors) ? fillColors[i] : fillColors;
        this.createRect(px, y, pWidth, height, fill, `${id}-sub-${i}`);
    }
}
Leg logic — update in 3 spots
  • drawPier() — regular piers P1–P11
  • drawAbutment() — abutments A1 / A2
  • drawPile() itself — bar count + per-bar color
Also fix pre-existing bug

drawPileFullView() hardcodes cols = 3 and ignores its own numOfRow param — in single_pier.blade.php, portal_pier.blade.php, double_portal_pier.blade.php. Change it to cols = Number(numOfRow), matching what double_pier.blade.php already does correctly — otherwise the dot-grid and the new legs will disagree on where column boundaries are.

If pile_pier_no isn't evenly divisible by pile_row_no, the last row is simply short a few dots (loop already guards with pileIndex < totalPiles) — no special-casing needed. The playground's "Rows × Cols" inputs always produce a full rectangle (total = rows × cols) so you can freely explore shapes; real data can still be a partial last row and the same logic handles it.

This is a static demo with sample data — not connected to the real database. The grey pier/wall block above each leg row is shown only for context and is unrelated to this change.