# Design: CapEx v2 — Sticky Scrollbar & Blendco Order Filter

**Date:** 2026-03-27
**Files affected:** `public/finance/capex_v2/capex_planning_fragment.php`, `public/finance/capex_v2/api/capex_planning_filters.php`

---

## Change 1: Always-Accessible Horizontal Scrollbar

### Problem
The planning table is wide (22 columns). The `overflow-x: auto` wrapper renders the browser's horizontal scrollbar at the physical bottom of the div. Users must scroll all the way to the bottom of the table before the scrollbar is reachable.

### Solution: Dual-scroll sync

Add a thin mirror div directly above the table wrapper that stays in the viewport at all times. A JS scroll-sync keeps both divs in lockstep.

### Implementation

**`capex_planning_fragment.php`**

1. Add `id="planningTableScroll"` to the existing `<div style="overflow-x: auto; ...">` table wrapper.
2. Insert a mirror div immediately above it:
   ```html
   <div id="planningTopScroll" style="overflow-x: auto; overflow-y: hidden; height: 12px;">
       <div id="planningTopScrollInner" style="height: 1px;"></div>
   </div>
   ```
3. Add `syncPlanningScroll()` JS function:
   - Sets `planningTopScrollInner.style.width` = `table.scrollWidth + 'px'`
   - Wires `scroll` event on each div; uses a boolean flag to prevent recursive sync
4. Call `syncPlanningScroll()` on `DOMContentLoaded`.
5. Chain `syncPlanningScroll()` after `refreshPlanningTable()` resolves — table width isn't known until AJAX rows are injected.

**Scope:** Fix applies to both standalone (`capex_planning.php`) and embedded (`tracker.php` tab2) since both `require` the fragment.

---

## Change 2: Blendco Order Filter (Planning Tab)

### Problem
`blendco_order` is a column in `capex_planning`, appears in the SELECT query and is rendered in the table, but has no corresponding filter control.

### Solution: Dynamic checkbox-list filter (same pattern as existing filters)

### Implementation

**`api/capex_planning_filters.php`**

1. Add variable: `$plan_filter_blendco = $_GET['plan_blendco'] ?? [];`
2. Add WHERE clause:
   ```php
   if (!empty($plan_filter_blendco)) {
       $formatted_blendco = '("' . implode('","', $plan_filter_blendco) . '")';
       $plan_query_search .= " AND po_list.blendco_order IN $formatted_blendco ";
       $plan_filtered .= " Blendco Order(" . $formatted_blendco . ")";
   }
   ```

**`capex_planning_fragment.php`**

1. Add a new filter cell in Row 2 (after Quote Confirmed):
   ```php
   <div class="capex-filter-cell">
       <b>Blendco Order</b><br>
       <details>
           <summary>Select Blendco Order</summary>
           <div class="checkbox-list">
               <?php
               $get_blendco = mysqli_query($conn, "SELECT DISTINCT blendco_order FROM capex_planning WHERE blendco_order IS NOT NULL AND blendco_order != '' ORDER BY blendco_order") or die(mysqli_error($conn));
               while ($row_blendco = mysqli_fetch_array($get_blendco)) {
                   $blendco = (string)($row_blendco['blendco_order'] ?? '');
                   $checked = in_array($blendco, $plan_filter_blendco) ? 'checked' : '';
                   echo '<label><input type="checkbox" name="plan_blendco[]" value="' . htmlspecialchars($blendco) . '" ' . $checked . '> ' . htmlspecialchars($blendco) . '</label>';
               }
               ?>
           </div>
       </details>
   </div>
   ```
2. Add `'plan_blendco'` to the `arrayKeys` array in `refreshPlanningTable()` JS so it's included in AJAX params.

---

## Files Changed

| File | Change |
|------|--------|
| `capex_planning_fragment.php` | Mirror div + sync JS; Blendco filter cell; arrayKeys update |
| `api/capex_planning_filters.php` | `$plan_filter_blendco` variable + WHERE clause |

## Out of Scope
- Tracker tab 1 (Purchase Orders) scrollbar — separate div, not requested
- Blendco Order filter on tracker tab 1 PO side — not requested
