# CLAUDE.md — IHBoxWebsite/app (Backbone.js SPA)

## Overview

This directory is the **Backbone.js Single Page Application** that runs inside the ASP.NET IHBox
shell. It contains ~109 custom screens covering the full meltshop operation (EAF, LMF, caster,
ladle tracking, chemistries, production reports, KPIs, etc.).

**Tech stack:** Backbone.js + Backbone.Epoxy · RequireJS (AMD) · Handlebars · Jed (i18n) ·
jQuery + jQuery UI · Backgrid.js · AG Grid Enterprise · Highcharts/Highstock · Chart.js · D3/DC

**No build pipeline** — JavaScript is plain ES5, served as individual files, loaded at runtime by
RequireJS. No TypeScript, no Webpack, no Babel, no bundler, no linter, no test runner.

---

## Directory Structure

```
app/
├── main.js                 # RequireJS entry point — bootstraps router, starts Backbone.history
├── app.js                  # Global app singleton: CallProcedure, logging, i18n, session
├── app-config.js           # IHBox-specific config: root paths, DB names, custom module list
├── require-config.js       # RequireJS path/shim config for all libraries
├── router.js               # Backbone.Router: view caching, URL resolution, role security
├── api/                    # Server-side ASPX API endpoints (code-behind C#)
├── components/             # Reusable UI components (ag-grid wrapper, modal variants)
├── custom-configs/         # Per-deployment config overrides (cenergy, elt, logbook, mes, oee…)
├── custom-modules/         # App-wide plugin modules loaded at startup (locks, menu, security…)
├── custom-screens/         # ~109 application screens (one folder per screen)
├── i18n/                   # resx2json.aspx converter + global translation .resx files
├── modules/                # ~77 shared Backbone modules (base, user, menu, controls, modals…)
├── pages/                  # Newer screens organized by area (caster, eaf, lmf, opc…)
├── screens/                # Framework screens + boilerplates (help-center, screen-boilerplate)
└── templates/              # Shared ASPX+Handlebars templates (menu, modal, navbar, controls…)
```

---

## Module System (AMD / RequireJS)

Every JS file is a RequireJS AMD module. Always use `define`, never `require` at module level:

```javascript
define([
    'app',                      // global app singleton — always include
    'js/templates-loader',      // async template loader — include in views
    'modules/some-module',      // project module (no .js extension)
    'backbone',                 // library alias from require-config.js
], function (app, T, SomeModule, Backbone) {
    // ...
    return MyModule;            // required — return the module for AMD compliance
});
```

Library aliases are defined in `require-config.js`. Never use relative paths to `assets/`; use
the configured alias (e.g., `backbone`, `jquery`, `underscore`, `highcharts`, `moment`).

---

## Screen Structure

Each screen lives in its own folder under `custom-screens/` (or `pages/` for newer screens):

```
custom-screens/my-screen/
├── my-screen.js                # Backbone module (the screen logic)
├── my-screen.aspx              # ASPX template (HTML + server-side i18n + CSS links)
├── my-screen.css               # Screen-specific styles
├── my-screen.sql               # Associated stored procedure(s) for reference
└── App_LocalResources/         # .resx translation files (EN default + ES, PT, FR)
    ├── my-screen.aspx.resx
    ├── my-screen.aspx.es.resx
    └── my-screen.aspx.pt.resx
```

Use `screens/screen-boilerplate/` as the starting template for new screens.
Use `modules/view-boilerplate.js` for new shared module views (non-screen components).

---

## View Lifecycle & Boilerplate

Every screen exports a namespace object with `generateID`, `Model`, and `Views.Main`:

```javascript
define(['app', 'js/templates-loader'], function (app, T) {

    var MyScreen = { Model: {}, Views: {} };

    // generateID: called by router to cache/identify the view instance.
    // Include URL param logic here if the same view shows different data per param.
    MyScreen.generateID = function (viewParams) {
        try { return 'my-screen'; } catch (Error) {}
    };

    MyScreen.Model = Backbone.Model.extend({ defaults: {} });

    MyScreen.Views.Main = Backbone.View.extend({
        template: 'my-screen'          // maps to custom-screens/my-screen/my-screen.aspx
        , id: 'my-screen'
        , title: 'My Screen'
        , isCacheable: true            // set true + implement reRender if caching is desired

        , initialize: function () {
            this.model = new MyScreen.Model();
            this.bindEvents();
            _.bindAll(this);
        }

        , events: { 'click #save-btn': 'onSave' }

        , render: function (container, viewParams) {
            var that = this;
            var customPath = '/app/custom-screens/my-screen/';
            T.render.call(this, this.template, function (tmp) {
                app.getI18NJed(that, that.template, function (i18n) {
                    that.options.i18n = that.options.i18n || {};
                    that.options.i18n[that.template] = i18n;
                    that.$el.html(tmp());
                    container.append(that.$el);
                    // initialize grids, charts, data fetch here
                });
            }, customPath);
        }

        , bindEvents: function () { /* listenTo calls go here */ }
        , preRender: function () { /* called before render; configure subnavbar here */ }
        , reRender: function (viewParams) { /* called on back/forward when isCacheable=true */ }
        , refresh: function (viewParams) { /* called when navigating to same view already shown */ }
        , close: function () { this.remove(); this.unbind(); }
        , show: function () { this.bindEvents(); this.$el.show(); }
        , hide: function () { this.$el.hide(); this.unbind(); this.stopListening(); }
    });

    return MyScreen;
});
```

---

## Data Access — `Core.Json.CallProcedure`

All data comes from SQL Server stored procedures. **Never write fetch/XHR calls directly.**

```javascript
var QP = new Core.Database.QueryParameters();
QP.Add('@heatId',      'INT',      heatId);
QP.Add('@furnaceCode', 'INT',      furnaceCode);
QP.Add('@startDate',   'DATETIME', startDate);

// Low-level call (IndustrialDashboard layer):
Core.Json.CallProcedure(
    app.DatabaseNames.MES + '.EAF.GetProcesses',   // 'DatabaseName.Schema.ProcedureName'
    QP,
    {
        onSuccess: function (resp) {
            // resp.Table  → first result set (array of row objects)
            // resp.Table1 → second result set, etc.
        },
        onFailure: function (resp) { console.error(resp); },
        Secured: true,
        Async: true,
    },
    app.ConnectionStrings.app   // connection string name ('APP' or 'SYSTEM')
);

// App-level wrapper (preferred) — adds SQL warning/error modal handling:
app.CallProcedure(
    app.DatabaseNames.MES + '.EAF.GetProcesses', QP,
    {
        onSuccess: function (resp) { /* ... */ },
        onFailure: function (resp) { /* ... */ },
        onFinally: function ()     { /* always runs */ },
    }
);
```

Use `app.ConnectionStrings.app` (read-only `APP`) for reads and `app.ConnectionStrings.system`
(`SYSTEM`) for writes. Database name constants are in `app-config.js`.

---

## Base Model / Collection (CRUD Pattern)

`modules/base/base.js` provides `Main.Models.Base` and `Main.Collections.Base` with built-in CRUD
wired to stored procedures. Extend them for domain models:

```javascript
var MyModel = Main.Models.Base.extend({
    procedures: {
        get:    app.DatabaseNames.MES + '.Schema.GetMyEntity',
        create: app.DatabaseNames.MES + '.Schema.CreateMyEntity',
        update: app.DatabaseNames.MES + '.Schema.UpdateMyEntity',
        remove: app.DatabaseNames.MES + '.Schema.DeleteMyEntity',
    },
    defaults: { id: null, name: '' }
});

var MyCollection = Main.Collections.Base.extend({
    model: MyModel,
    pageSize: 50,   // server-side pagination via @fromRow / @rowsToFetch params
});
```

Collections support server-side pagination (`currentPage`, `pageSize`, `getNextPage()`).
`parseResponse()` maps `resp.Table` rows into Backbone model attributes.

---

## Backbone.Epoxy (Data Binding — Newer Screens)

Newer screens use `Backbone.Epoxy` for declarative two-way binding:

```javascript
MyScreen.Model = Backbone.Epoxy.Model.extend({
    defaults: { name: '', isValid: false },
    computeds: {
        displayName: function () { return this.get('name').toUpperCase(); }
    }
});

MyScreen.Views.Main = Backbone.Epoxy.View.extend({
    bindings: 'data-bind',
    // HTML: <input data-bind="value:name"> <span data-bind="text:displayName">
});
```

---

## AG Grid Enterprise (Newer Screens)

Screens in `pages/` use a **Backbone.View wrapper** around AG Grid Enterprise located at
`components/ag-grid/ag-grid-module.js`. The theme is `ag-theme-alpine`; custom property overrides
live in `components/ag-grid/ag-grid-styles.css`. The AMD alias is `'agGrid'` (configured in
`require-config.js`). Screens in `custom-screens/` use the older **Backgrid.js** pattern instead.

### Import & Instantiation

```javascript
define([
    'app',
    'js/templates-loader',
    'components/ag-grid/ag-grid-module',  // the wrapper
], function (app, T, AgGridModule) {

    // Pattern A — direct use (simple screens):
    , initialize: function () {
        this.agGridModule = new AgGridModule({
            columnDefs: this.columns,
            rowData: [],
            fileName: 'my-screen',        // Excel export filename prefix
            screenTitle: 'My Screen',
            pagination: true,             // true | 'hidden'
            allowMultipleSelection: true,
            onChange: this.onCellChanged.bind(this),
            onRemove: this.onGridRemove.bind(this),
            gridOptions: { /* any AG Grid gridOptions overrides */ }
        });
    }

    // Pattern B — extend the wrapper for custom cell renderers / context menu:
    var CustomAgGridModule = AgGridModule.extend({
        initialize: function (options) {
            AgGridModule.prototype.initialize.call(this, options);
        },
        getContextMenuItems: function (params) {
            return ['copy', 'separator',
                { name: 'Export All to Excel', action: function () { /* ... */ } }];
        },
        onCellClicked: function (params) {
            if (params.column.colDef.field === 'HeatName') {
                app.router.navigate('!/heat/' + params.value, { trigger: true });
                return;
            }
            AgGridModule.prototype.onCellClicked.call(this, params);
        }
    });
});
```

### Column Definitions

Use standard AG Grid properties plus these **custom `fieldType` values** — the wrapper's
`setRowData()` formats them automatically before passing rows to the grid:

```javascript
columns: [
    { field: 'Heat',       headerName: 'Heat',        flex: 1, width: 94, pinned: 'left' },
    { field: 'Start',      headerName: 'Start',        flex: 1, minWidth: 115,
      fieldType: 'Datetime', format: 'MM/DD HH:mm' },   // moment.js format string
    { field: 'Weight',     headerName: 'Weight',       flex: 1, minWidth: 90,
      fieldType: 'Float',    decimals: 1 },              // toFixed(decimals)
    { field: 'Duration',   headerName: 'Duration',     flex: 1,
      fieldType: 'Seconds' },                            // formats int seconds -> HH:MM:SS
    { field: 'Count',      headerName: 'Count',        flex: 1,
      fieldType: 'Integer' },
    { field: 'edit',       headerName: '',             width: 40,
      fieldType: 'edit' },                               // renders a pencil icon button
    // Standard AG Grid cellRenderer for custom HTML:
    { field: 'Status', headerName: 'Status', flex: 1,
      cellRenderer: function (params) {
          var color = params.value === 'OK' ? 'green' : 'red';
          return '<span style="color:' + color + '">' + params.value + '</span>';
      }
    },
    // Conditional row styling:
    { field: 'Flag', cellStyle: function (params) {
          return params.value === 'Y'
              ? { backgroundColor: '#b3ffb3', textAlign: 'center' }
              : { textAlign: 'center' };
    }},
]
```

### Data Loading (Collection -> AG Grid)

```javascript
// 1. Show loading overlay while fetching:
this.agGridModule.showLoadingOverlay();

// 2. Fetch data via Backbone Collection (calls stored procedure internally):
this.itemsColl.fetch({ start: startDate, end: endDate });

// 3. Handle the collection's 'fetch' event:
, bindEvents: function () {
    this.listenTo(this.itemsColl, 'fetch', this.onDataReady);
}

// 4. Bridge Backbone Collection -> AG Grid:
, onDataReady: function () {
    var that = this;
    setTimeout(function () {                            // 100ms delay ensures DOM is ready
        that.agGridModule.setRowDataAsCollection(that.itemsColl);
        that.agGridModule.hideOverlay();
    }, 100);
}
```

For plain arrays (not collections) use `this.agGridModule.setRowData(arrayOfObjects)` directly.

### Rendering (Appending the Grid into the Screen)

The grid is a Backbone subview. Render it inside the screen's `render()` callback and append it
into a container div defined in the screen's ASPX template:

```javascript
// In the screen's ASPX template:
// <div class="processes-grid-container ag-theme-alpine w-full" style="height:75vh;"></div>

, render: function (container) {
    var that = this;
    T.render.call(this, this.template, function (tmp) {
        that.$el.html(tmp());
        container.append(that.$el);
        // Append the grid subview into the container div:
        that.$el.find('.processes-grid-container').append(that.agGridModule.$el);
        that.agGridModule.render(that.$el.find('.processes-grid-container'));
        // Trigger initial data fetch:
        that.refreshData();
    }, '/app/pages/my-area/my-screen/');
}
```

### `gridReadyPromise` — Safe API Access

The wrapper exposes a Promise that resolves once `onGridReady` fires. Use it before calling
`gridApi` methods directly (e.g., for real-time row updates):

```javascript
this.agGridModule.gridReadyPromise.then(function () {
    var rowNode = that.agGridModule.gridApi.getRowNode(nodeId);
    if (rowNode) {
        rowNode.setDataValue('Value', newValue);
        that.agGridModule.gridApi.refreshCells({ rowNodes: [rowNode], force: true });
    }
});
```

### AG Grid vs. Backgrid (Quick Reference)

| Aspect | Backgrid (`custom-screens/`) | AG Grid (`pages/`) |
|---|---|---|
| Grid creation | Inside `render()`, bound to Backbone Collection | In `initialize()`, data loaded via `setRowDataAsCollection()` |
| Column defs | `name`, `label`, `cell: Backgrid.XCell` | `field`, `headerName`, `fieldType`, `cellRenderer` |
| Pagination | Server-side (`@fromRow`/`@rowsToFetch`) | Client-side (built-in AG Grid) |
| Styling | Bootstrap 2.x table classes | `ag-theme-alpine` + Tailwind utilities |
| Excel export | Manual | Built-in Enterprise (`exportDataAsExcel()`) |
| Context menu | jQuery contextMenu plugin | `getContextMenuItems` override |

---

## Internationalization (i18n)

- Translation source: `.resx` files in each screen's `App_LocalResources/` folder.
- Runtime: `resx2json.aspx` converts `.resx` to Jed-compatible JSON on request.
- Languages: **English** (default), **Spanish** (`es`), **Portuguese** (`pt`), **French** (`fr`).

```javascript
// Load i18n for a template (called inside render):
app.getI18NJed(that, that.template, function (i18n) {
    var label = i18n.gettext('Save');   // looks up key in loaded .resx
});

// Short-hand anywhere in the app:
app.translate('key');
```

ASPX templates use `<asp:Localize runat="server" meta:resourcekey="SaveButton.Text" />` for
server-side rendered strings. Always add keys to all four language `.resx` files.

---

## Routing

Routes are **database-driven** — loaded from `FrontEnd.GetRoutes` stored procedure at startup.
Format: `!/screen-name(/:param1)(/:param2)(/)` (hashbang with optional segments).

The router (`router.js`):
- Caches up to 4 view instances (`viewsCacheMaxLength`) for back/forward performance.
- Enforces role-based security via `routesPolicies` before instantiating a view.
- Lazy-loads screen modules via `require()` at routing time.
- Calls `preRender` → `render` (or `reRender` if cached) → `show` in sequence.

---

## CSS & Styling

- Each screen has its own `.css` file; link it in the screen's `.aspx` template.
- Layout uses **Bootstrap 2.x** grid classes: `row-fluid`, `span6`, `offset2`, etc.
- No CSS preprocessor pipeline — edit `.css` directly (`.less` source files exist for some
  older screens but there is no automated compile step).
- Class names are lowercase-hyphenated: `.heat-summary-grid`, `.align-center-cell`.
- Avoid inline styles; use the screen's CSS file or Bootstrap utilities.
- AG Grid theming: override via CSS custom properties (`--ag-header-height`, etc.).

---

## Naming Conventions (JavaScript)

| Element | Convention | Example |
|---|---|---|
| Screen/module namespace | PascalCase | `LinesProduction`, `EafProcess` |
| View properties | camelCase | `isCacheable`, `template`, `id` |
| Local variables | camelCase | `that`, `thatContainer`, `heatId` |
| DOM element IDs | lowercase-hyphen | `#save-btn`, `#heat-grid` |
| CSS classes | lowercase-hyphen | `.heat-summary-row` |
| Stored procedure ref | `DB.Schema.ProcName` | `IH_100020.EAF.GetProcesses` |
| RequireJS module path | lowercase-hyphen, no ext | `'modules/date-control'` |

---

## Error Handling (Client-Side)

- Wrap event handlers and lifecycle methods in `try { ... } catch (Error) { }` to prevent one
  broken screen from crashing the entire SPA.
- Log errors to the server via `app.log.error(message)` (routes through `Log.svc` WCF service).
- Use `app.serverResponseToConsole(resp)` during development to inspect raw server responses.
- `app.CallProcedure` automatically shows a user-facing modal for SQL warnings (50000–51000)
  and errors (>51000) — do not duplicate that handling in individual screens.

---

## Key Global Objects

| Object | Source | Purpose |
|---|---|---|
| `app` | `app.js` | Singleton: config, CallProcedure, logging, i18n, session |
| `app.models.user` | `modules/user.js` | Authenticated user info and roles |
| `app.models.menu` | `modules/menu.js` | Navigation menu model |
| `app.DatabaseNames` | `app-config.js` | DB name constants (`MES`, `IHBoxSystem`, etc.) |
| `app.ConnectionStrings` | `app-config.js` | Connection string name constants |
| `Core.Json` | IndustrialDashboard | Low-level stored procedure caller |
| `Core.Database` | IndustrialDashboard | `QueryParameters` builder |
| `Backbone` | `assets/libs/jam/` | MV* framework (global, no import needed) |
| `_` | `assets/libs/jam/` | Underscore.js utilities (global) |
| `$` | `assets/libs/jam/` | jQuery (global) |
