[UI] Bootstrap 5 Migration – Phase 7: SKU Alias (Create, Convert, Detail, Linked SKUs) #85

Open
opened 2026-06-29 08:36:20 +00:00 by rob · 0 comments
Owner

Summary

Migrate the SKU Alias pages to Bootstrap 5. This covers four distinct pages/views:

  1. Create SKU Alias (standalone page)
  2. Convert existing SKU to alias (standalone page)
  3. SKU Alias detail with tabs (Info, Linked SKUs, Sales, Images, Attributes)
  4. SKU Alias tab on normal product detail page (read-only list)

Stack: PHP / Laminas MVC. Templates are .phtml files.


Attached Files

SKU_Alias_-_UI_Phase_7.zip

File What to do with it
sku-alias-preview.html HTML preview — use demo bar to switch between all 5 views. Verify before implementing.
sku-alias-create.phtml Create SKU Alias page — new file
sku-alias-convert.phtml Convert SKU to Alias page — new file
sku-alias-shell.phtml Alias detail shell (tabs) — new file
sku-alias-info.phtml Alias detail Info tab — new file
sku-alias-linked-skus.phtml Alias detail Linked SKUs tab — new file
product-sku-alias-tab.phtml SKU Alias tab on normal product detail — new file
package.json Grunt dependencies — replace
Gruntfile.js Grunt build pipeline — replace
scss/main.scss SCSS entry point — replace
scss/_tokens.scss Design tokens — replace
scss/_base.scss Base styles — replace
scss/_components.scss Component styles — replace
js/main.js Global JS — replace

File Placement

module/SkuAlias/view/sku-alias/create/index.phtml    ← sku-alias-create.phtml
module/SkuAlias/view/sku-alias/convert/index.phtml   ← sku-alias-convert.phtml
module/SkuAlias/view/sku-alias/detail/shell.phtml    ← sku-alias-shell.phtml
module/SkuAlias/view/sku-alias/detail/info.phtml     ← sku-alias-info.phtml
module/SkuAlias/view/sku-alias/detail/linked-skus.phtml ← sku-alias-linked-skus.phtml
module/Products/view/products/detail/sku-alias.phtml ← product-sku-alias-tab.phtml

Page 1 — Create SKU Alias (/sku-alias/create)

Two-column layout. Left: form. Right: two separate cards.

Left form fields:

  • SKU Alias* (required, free text)
  • Title* (required)
  • UPC (optional)
  • EAN (optional)
  • Save button (green primary)

Right — Card 1: Linked SKUs table (SKU / Description / Qty / Action)

  • Qty is an editable number input per row
  • Remove button: btn-es-slate (outlined slate, not red) with confirm dialog
  • Footer shows: "Available to sell: N" — calculated as min(floor(component_stock / component_qty)) across all linked components

Right — Card 2: Add Component SKUs

  • Add Product search field with Search button (green, attached right)
  • Results dropdown appears below field: SKU — Product name [available qty]
  • Quantity input
  • Link SKU button (green primary)

Controller endpoint:

  • GET /sku-alias/search?q= — returns JSON [{'sku','name','product_id','available'}, ...]

Page 2 — Convert SKU to Alias (/sku-alias/convert/{id})

Same layout as Create with two differences:

  • SKU Alias* field is pre-populated from the existing SKU and locked (read-only, grey background, "cannot be changed" note)
  • Title is pre-populated from existing product name but editable
  • Has Cancel button (links to /products) alongside Save button
  • After saving, user adds component SKUs on the Linked SKUs tab to complete conversion

Page 3 — SKU Alias detail (/sku-alias/{id})

Own product detail shell with tabs. Tabs: Info · Linked SKUs · Sales · Images · Attributes.

No Inventory, Locations, Suppliers, or Listings tabs — these do not apply to alias SKUs.

Info tab — editable form: SKU Alias, Title, UPC, EAN, Discontinued checkbox, Save button.

Linked SKUs tab — same two-card layout as Create page (Linked SKUs table + Add Component SKUs). Qty editable inline. Remove is slate outlined. Available to sell footer calculates from component stock.

Sales tab — identical to Phase 5 product-sales.phtml. Reuse as-is with alias product_id.

Images tab — identical to Phase 6 product-images.phtml. Reuse as-is with alias product_id.

Attributes tab — identical to Phase 6 product-attributes.phtml. Reuse as-is with alias product_id.


Page 4 — SKU Alias tab on normal product (/products/{id}/sku-alias)

Read-only table: SKU Alias / Title. Each alias SKU links to its own detail page (/sku-alias/{id}).

Add to the normal product detail shell tab list (after Suppliers):

'sku-alias' => ['SKU Alias', 'products/sku-alias']

Helper text below table:

These are all SKU aliases in eStack that contain this SKU as a component. Click any alias to view or edit it.


Linked SKUs search — AJAX endpoint

Required for both Create and Linked SKUs tab:

GET /sku-alias/search?q=<search term>

Returns JSON:

[
  {"sku": "200654", "name": "CBUS 16ft USB-C Cable...", "product_id": 123, "available": 395},
  ...
]

Searches product SKU and name. Results display as SKU — Name [available qty] in the dropdown.


New CSS classes needed in _components.scss

// Two-column alias layout
.es-alias-layout {
  display: grid;
  grid-template-columns: 1fr 400px;
  gap: 28px;
  align-items: start;
  max-width: 1100px;
}

// Qty input in linked SKUs table
.es-qty-sm {
  width: 70px; height: 30px; padding: 0 8px;
  border: 1px solid var(--es-border); border-radius: var(--es-radius);
  font-size: 12.5px; font-family: inherit;
  background: var(--es-surface); color: var(--es-text); text-align: right;
  &:focus { outline: none; border-color: var(--es-green); }
}

// Available to sell footer inside linked SKUs card
.es-avail-footer {
  padding: 10px 16px; border-top: 1px solid var(--es-border);
  font-size: 12px; color: var(--es-muted); text-align: right;
  strong { color: var(--es-green); font-size: 13px; }
}

// Search field with attached button (also used in shipment detail add product)
.es-search-field-wrap { position: relative; display: flex; }
.es-search-field {
  flex: 1; height: 36px; padding: 0 80px 0 10px;
  border: 1px solid var(--es-border); border-radius: var(--es-radius);
  font-size: 12.5px; font-family: inherit;
  background: var(--es-surface); color: var(--es-text);
  &:focus { outline: none; border-color: var(--es-green); box-shadow: 0 0 0 3px rgba(26,122,74,.1); }
}
.es-search-field-btn {
  position: absolute; right: 0; top: 0; height: 36px; padding: 0 14px;
  background: var(--es-green); color: #fff; border: none;
  border-radius: 0 var(--es-radius) var(--es-radius) 0;
  font-size: 12.5px; font-weight: 500; cursor: pointer;
  &:hover { background: var(--es-green-hover); }
}
.es-search-results {
  border: 1px solid var(--es-border); border-top: none;
  border-radius: 0 0 var(--es-radius) var(--es-radius);
  background: var(--es-surface); display: none;
  max-height: 240px; overflow-y: auto;
  &.open { display: block; }
}
.es-search-result-item {
  padding: 9px 12px; cursor: pointer; font-size: 12.5px;
  border-bottom: 1px solid var(--es-border);
  display: flex; gap: 10px; align-items: baseline;
  &:last-child { border-bottom: none; }
  &:hover { background: var(--es-green-light); }
}
.es-search-result-sku { font-family: var(--es-mono); color: var(--es-green); font-weight: 700; font-size: 12px; flex-shrink: 0; min-width: 70px; }
.es-search-result-name { color: var(--es-text); font-size: 12px; line-height: 1.4; }

Listings tab — remove from existing alias pages

The old SKU Alias detail had a Listings tab. Remove it — it is not part of the new design.


Build

After replacing all files run:

npm install
grunt build

Definition of Done

  • grunt build runs with zero errors
  • All five views render correctly in Chrome and Firefox, light and dark mode
  • Create: form saves, linked SKU search works, Link SKU adds row, Remove is slate outlined
  • Convert: SKU Alias field locked/read-only, Cancel returns to /products
  • Alias detail: 5 tabs only (Info, Linked SKUs, Sales, Images, Attributes) — no Inventory/Locations/Suppliers/Listings
  • Sales, Images, Attributes tabs reuse Phase 5/6 phtml files unchanged
  • Linked SKUs available-to-sell calculated from component stock
  • Normal product SKU Alias tab: read-only, links to alias detail page
  • MR reviewed against sku-alias-preview.html before merge
## Summary Migrate the SKU Alias pages to Bootstrap 5. This covers four distinct pages/views: 1. Create SKU Alias (standalone page) 2. Convert existing SKU to alias (standalone page) 3. SKU Alias detail with tabs (Info, Linked SKUs, Sales, Images, Attributes) 4. SKU Alias tab on normal product detail page (read-only list) **Stack: PHP / Laminas MVC. Templates are `.phtml` files.** --- ## Attached Files [SKU_Alias_-_UI_Phase_7.zip](/uploads/5c532118a769d9dbbb47a9f250a05187/SKU_Alias_-_UI_Phase_7.zip) | File | What to do with it | |---|---| | `sku-alias-preview.html` | HTML preview — use demo bar to switch between all 5 views. Verify before implementing. | | `sku-alias-create.phtml` | Create SKU Alias page — new file | | `sku-alias-convert.phtml` | Convert SKU to Alias page — new file | | `sku-alias-shell.phtml` | Alias detail shell (tabs) — new file | | `sku-alias-info.phtml` | Alias detail Info tab — new file | | `sku-alias-linked-skus.phtml` | Alias detail Linked SKUs tab — new file | | `product-sku-alias-tab.phtml` | SKU Alias tab on normal product detail — new file | | `package.json` | Grunt dependencies — replace | | `Gruntfile.js` | Grunt build pipeline — replace | | `scss/main.scss` | SCSS entry point — replace | | `scss/_tokens.scss` | Design tokens — replace | | `scss/_base.scss` | Base styles — replace | | `scss/_components.scss` | Component styles — replace | | `js/main.js` | Global JS — replace | --- ## File Placement module/SkuAlias/view/sku-alias/create/index.phtml ← sku-alias-create.phtml module/SkuAlias/view/sku-alias/convert/index.phtml ← sku-alias-convert.phtml module/SkuAlias/view/sku-alias/detail/shell.phtml ← sku-alias-shell.phtml module/SkuAlias/view/sku-alias/detail/info.phtml ← sku-alias-info.phtml module/SkuAlias/view/sku-alias/detail/linked-skus.phtml ← sku-alias-linked-skus.phtml module/Products/view/products/detail/sku-alias.phtml ← product-sku-alias-tab.phtml --- ## Page 1 — Create SKU Alias (`/sku-alias/create`) Two-column layout. Left: form. Right: two separate cards. **Left form fields:** - SKU Alias* (required, free text) - Title* (required) - UPC (optional) - EAN (optional) - Save button (green primary) **Right — Card 1: Linked SKUs table** (SKU / Description / Qty / Action) - Qty is an editable number input per row - Remove button: `btn-es-slate` (outlined slate, not red) with confirm dialog - Footer shows: "Available to sell: N" — calculated as min(floor(component_stock / component_qty)) across all linked components **Right — Card 2: Add Component SKUs** - Add Product search field with Search button (green, attached right) - Results dropdown appears below field: `SKU — Product name [available qty]` - Quantity input - Link SKU button (green primary) **Controller endpoint:** - `GET /sku-alias/search?q=` — returns JSON `[{'sku','name','product_id','available'}, ...]` --- ## Page 2 — Convert SKU to Alias (`/sku-alias/convert/{id}`) Same layout as Create with two differences: - SKU Alias* field is pre-populated from the existing SKU and **locked** (read-only, grey background, "cannot be changed" note) - Title is pre-populated from existing product name but **editable** - Has **Cancel** button (links to `/products`) alongside Save button - After saving, user adds component SKUs on the Linked SKUs tab to complete conversion --- ## Page 3 — SKU Alias detail (`/sku-alias/{id}`) Own product detail shell with tabs. **Tabs: Info · Linked SKUs · Sales · Images · Attributes.** No Inventory, Locations, Suppliers, or Listings tabs — these do not apply to alias SKUs. **Info tab** — editable form: SKU Alias, Title, UPC, EAN, Discontinued checkbox, Save button. **Linked SKUs tab** — same two-card layout as Create page (Linked SKUs table + Add Component SKUs). Qty editable inline. Remove is slate outlined. Available to sell footer calculates from component stock. **Sales tab** — identical to Phase 5 `product-sales.phtml`. Reuse as-is with alias product_id. **Images tab** — identical to Phase 6 `product-images.phtml`. Reuse as-is with alias product_id. **Attributes tab** — identical to Phase 6 `product-attributes.phtml`. Reuse as-is with alias product_id. --- ## Page 4 — SKU Alias tab on normal product (`/products/{id}/sku-alias`) Read-only table: SKU Alias / Title. Each alias SKU links to its own detail page (`/sku-alias/{id}`). Add to the normal product detail shell tab list (after Suppliers): 'sku-alias' => ['SKU Alias', 'products/sku-alias'] Helper text below table: > These are all SKU aliases in eStack that contain this SKU as a component. Click any alias to view or edit it. --- ## Linked SKUs search — AJAX endpoint Required for both Create and Linked SKUs tab: GET /sku-alias/search?q=<search term> Returns JSON: [ {"sku": "200654", "name": "CBUS 16ft USB-C Cable...", "product_id": 123, "available": 395}, ... ] Searches product SKU and name. Results display as `SKU — Name [available qty]` in the dropdown. --- ## New CSS classes needed in `_components.scss` // Two-column alias layout .es-alias-layout { display: grid; grid-template-columns: 1fr 400px; gap: 28px; align-items: start; max-width: 1100px; } // Qty input in linked SKUs table .es-qty-sm { width: 70px; height: 30px; padding: 0 8px; border: 1px solid var(--es-border); border-radius: var(--es-radius); font-size: 12.5px; font-family: inherit; background: var(--es-surface); color: var(--es-text); text-align: right; &:focus { outline: none; border-color: var(--es-green); } } // Available to sell footer inside linked SKUs card .es-avail-footer { padding: 10px 16px; border-top: 1px solid var(--es-border); font-size: 12px; color: var(--es-muted); text-align: right; strong { color: var(--es-green); font-size: 13px; } } // Search field with attached button (also used in shipment detail add product) .es-search-field-wrap { position: relative; display: flex; } .es-search-field { flex: 1; height: 36px; padding: 0 80px 0 10px; border: 1px solid var(--es-border); border-radius: var(--es-radius); font-size: 12.5px; font-family: inherit; background: var(--es-surface); color: var(--es-text); &:focus { outline: none; border-color: var(--es-green); box-shadow: 0 0 0 3px rgba(26,122,74,.1); } } .es-search-field-btn { position: absolute; right: 0; top: 0; height: 36px; padding: 0 14px; background: var(--es-green); color: #fff; border: none; border-radius: 0 var(--es-radius) var(--es-radius) 0; font-size: 12.5px; font-weight: 500; cursor: pointer; &:hover { background: var(--es-green-hover); } } .es-search-results { border: 1px solid var(--es-border); border-top: none; border-radius: 0 0 var(--es-radius) var(--es-radius); background: var(--es-surface); display: none; max-height: 240px; overflow-y: auto; &.open { display: block; } } .es-search-result-item { padding: 9px 12px; cursor: pointer; font-size: 12.5px; border-bottom: 1px solid var(--es-border); display: flex; gap: 10px; align-items: baseline; &:last-child { border-bottom: none; } &:hover { background: var(--es-green-light); } } .es-search-result-sku { font-family: var(--es-mono); color: var(--es-green); font-weight: 700; font-size: 12px; flex-shrink: 0; min-width: 70px; } .es-search-result-name { color: var(--es-text); font-size: 12px; line-height: 1.4; } --- ## Listings tab — remove from existing alias pages The old SKU Alias detail had a Listings tab. Remove it — it is not part of the new design. --- ## Build After replacing all files run: npm install grunt build --- ## Definition of Done - [ ] `grunt build` runs with zero errors - [ ] All five views render correctly in Chrome and Firefox, light and dark mode - [ ] Create: form saves, linked SKU search works, Link SKU adds row, Remove is slate outlined - [ ] Convert: SKU Alias field locked/read-only, Cancel returns to `/products` - [ ] Alias detail: 5 tabs only (Info, Linked SKUs, Sales, Images, Attributes) — no Inventory/Locations/Suppliers/Listings - [ ] Sales, Images, Attributes tabs reuse Phase 5/6 phtml files unchanged - [ ] Linked SKUs available-to-sell calculated from component stock - [ ] Normal product SKU Alias tab: read-only, links to alias detail page - [ ] MR reviewed against `sku-alias-preview.html` before merge
rob added the QA label 2026-06-29 08:36:20 +00:00
Sign in to join this conversation.