# Convert "API Maker UI Maker" (JSON-config-driven grid + form) to hand-written Angular + PrimeNG

Source: https://docs.apimaker.dev/prompts/UI_Maker_to_primeng_conversion.html

## 1. Title & Objective

You are an AI coding agent. Your task is to **convert an API Maker "UI Maker" page — a single JSON configuration object (`IDBMasterConfig`, conventionally the variable `dbMasterConfig`) that drives a PrimeNG data grid plus add/edit/view reactive forms inside an iframe — into a normal, explicit, hand-written Angular + PrimeNG application**: a `p-table`-based grid component and reactive `FormGroup`-based form components, where every feature currently expressed in JSON is implemented directly in Angular/PrimeNG TypeScript and templates.

**Non-negotiable rule: ZERO FEATURE LOSS.** Every form control, every property, every validation rule, every file-upload behavior, every dependent/cascading dropdown, every conditional show/hide, every dynamic-required rule, every nested field, every nested grid, every add-new-inline form, every API-call override, every CSV import/export behavior, every theme, every iframe/postMessage event, every row-level action-hiding flag, every custom action button, every grid grouping/sorting/pagination/selection/filter/search/column-selector behavior, and every operations-ordering rule **must be reproduced exactly**. Do not invent features that are not in the source config. Where the source config is ambiguous, **inspect the original `dbMasterConfig` for that page** and reproduce the observed runtime behavior.

Work **page-by-page**: for each UI Maker page you are given (identified by `db-master-name`), produce a dedicated Angular feature module/standalone component set. Read that page's `dbMasterConfig` in full first — it is the source of truth that overrides any general guidance below.

---

## 2. Background: What UI Maker is

UI Maker is an extension layered on top of **API Maker**. It takes one `IDBMasterConfig` object and renders, at runtime, a complete CRUD screen for a database table/collection:

- A **PrimeNG data grid** (`grid` config) with columns, sorting, filtering, global search, pagination, grouping, row selection, row actions (edit/view/delete + custom), and a toolbar (add/import/export/refresh/column-selector/theme-selector/delete-all + custom actions).
- **Reactive add/edit/view forms** (`form.fields`, a **2-D array**: outer = rows, inner = columns) built from **24 control types** (`EDBMasterFormControl`).
- Each field is bound to a dot-notation `path`, supports static or **JS-expression-string** `visible`/`disabled`/`requiredFun`, can nest other fields (`fields[][]`) or a whole child grid (`gridSettings`).
- Pages run **inside an iframe** and talk to the host via `window.postMessage`. They call **API Maker auto-generated POST endpoints** for all data operations, each overridable via `apiCallOverrides`.
- Custom **CSS** (`cssCode[{runOn:'header', code}]`) and **JS lifecycle hooks** (`jsCode[{appendTo, code}]`) can be injected globally (page) or per control. Code can be inline strings, functions, or references to external named functions (`userUtils.Main.*` per-page, `userUtils.GeneralUtils.*` shared).
- Inbuilt libraries available to custom code: **Papa Parse** (`Papa`), **date-fns** (`dateFns`), **date-fns-tz** (`dateFnsTz`); commonly added: **lodash** (`_`), **moment**. Inbuilt CSS: **Bootstrap 5.3.3 grid + utilities**.

UI Maker **cannot run standalone** — it is coupled to the API Maker backend that supplies data. Your conversion must therefore also implement an explicit Angular **HttpClient service layer** that calls the same backend endpoints.

---

## 3. Target architecture

Implement this structure (standalone Angular components, PrimeNG, `ReactiveFormsModule`):

```
src/app/
  ui-maker-shared/                  # shared, imported by EVERY converted page
    services/
      api.service.ts                # HttpClient layer; replaces all API Maker calls + apiCallOverrides
      query.service.ts              # builds API Maker IQueryFormat (find/select/limit/deep/sort) bodies
      parent-integration.service.ts # postMessage send/receive (iframe contract) OR @Input/@Output bridge
      expression.service.ts         # safe evaluator for visible/disabled/requiredFun JS-expression strings
      hook-runner.service.ts        # executes jsCode (string | function | userUtils.* ref) with $scope
      message.service.ts            # toast wrapper -> utils.messageService.*
      file-upload.service.ts        # upload/download/remove contract
      theme.service.ts              # theme switching
      date-format.service.ts        # date-fns <-> PrimeNG calendar format + tz conversion
    utils/
      general-utils.ts              # <= userUtils.GeneralUtils.* (shared common JS)
      form-path.util.ts             # dot-notation path <-> nested FormGroup helpers
    components/
      um-grid/                      # reusable generic p-table grid component (config-light, typed)
      um-form/                      # reusable form renderer OR base class for per-page forms
      controls/                     # one component per EDBMasterFormControl (24)
  pages/
    <db-master-name>/               # ONE per UI Maker page
      <name>.component.ts/.html/.scss   # page component: grid + form wiring, page jsCode hooks, page cssCode
      <name>.main.ts                # <= userUtils.Main.* (page-specific JS functions)
      <name>.api.ts                 # page-specific endpoints / overrides
      <name>.model.ts               # typed row + form model interfaces
  styles.scss                       # global: Bootstrap grid+utilities + shared cssCode (common CSS)
```

**Recommended approach — per-page hand-written components backed by reusable building blocks.** Build a small library of reusable, typed primitives (a generic `<um-grid>`, the 24 control components, the shared services), then **hand-write one explicit component per UI Maker page** that composes them with the page's exact behavior. Rationale: a fully generic runtime engine would just re-implement UI Maker; explicit per-page components are debuggable, type-safe, tree-shakeable, and let you delete dead config. Reuse the primitives so you do not re-implement PrimeNG wiring 24 times.

Key mappings:

- **Reactive forms**: each form is a `FormGroup`. Dot-notation `path` (`a.b.c`) maps to **nested `FormGroup`s** (`form.get('a.b.c')`). Provide `form-path.util.ts` to build the nested group from all field `path`s and to get/set by path.
- **2-D `fields[][]` layout**: outer array = a `<div class="row">`; inner array = the columns inside that row. Each field wrapper gets `cssClassDiv` classes (default `col-lg mt-4 col-md-{calculated based on columns.length}` — compute the `col-md-*` span from the number of columns in that inner array). Bootstrap 5.3.3 grid is the layout system.
- **API layer** replaces API Maker auto-generated calls and `apiCallOverrides` (see §9).
- **Parent integration** (`parent-integration.service.ts`) reproduces the iframe `postMessage` contract (see §12). If the converted app is no longer embedded as an iframe, replace inbound parent events with component `@Input()`s / route/query params and outbound events with `@Output()` `EventEmitter`s — but **preserve the same semantics and payloads**.

---

## 4. Global vs Page-specific CSS & JS  (READ CAREFULLY — explicit requirement)

UI Maker has **two tiers** of custom CSS and JS: **common/shared** (used across many or all pages) and **page-specific**. You must split them correctly.

### 4.1 CSS

| Source (UI Maker) | What it is | Target (Angular) |
|---|---|---|
| **Common/global CSS** used across many pages | shared style overrides | Put into the **global `styles.scss`** (or a shared `_um-shared.scss` imported there). Loaded once, applies everywhere. |
| **Page-specific CSS**: `config.cssCode: [{ runOn: 'header', code: '<raw CSS>' }]` | raw CSS injected into `<head>` for that page | Put into **that page component's stylesheet** (`<name>.scss`). Note `runOn` is always `'header'`. If a class must override already-applied global/theme classes (UI Maker allows overriding existing classes), keep the same selectors and use `:host ::ng-deep` or move it to global scope as needed to win specificity. |
| Inbuilt **Bootstrap 5.3.3 grid + utilities** | `col-*`, `mt-*`, `row`, etc. used by `cssClassDiv` | Ensure Bootstrap **grid + utilities** CSS is loaded globally so `cssClassDiv` classes keep working. |

Mapping example:

```jsonc
// BEFORE (UI Maker page config)
cssCode: [{ runOn: 'header', code: `.font-orange { color: orange; }` }]
```
```scss
/* AFTER — <name>.scss (page-specific) */
.font-orange { color: orange; }
```

### 4.2 JS

UI Maker JS comes in three forms for the `code` value: an **inline JS string**, a **function** `($scope) => any`, or a **reference to a named external function** (`userUtils.Main.fnName` or `userUtils.GeneralUtils.fnName`). Code may **mutate the provided data in place**, **return a `Promise`** (await it), or **return a function** (invoke it).

| Source (UI Maker) | What it is | Target (Angular) |
|---|---|---|
| **Common/global JS**: `userUtils.GeneralUtils.<fn>` | shared utilities callable from any page via `code: 'userUtils.GeneralUtils.fn'` | Implement as methods/exports in **`ui-maker-shared/utils/general-utils.ts`**, imported by all pages. |
| **Page-specific JS (named)**: `userUtils.Main.<fn>` | per-page functions (e.g. `userUtils.Main.first_name_blur`) referenced from config | Implement as methods on **that page's `<name>.main.ts`** (or the page component). |
| **Page-level lifecycle hooks**: top-level `config.jsCode: [{ appendTo: EDBMasterConfigAppendTo, code }]` | run at named page lifecycle points | Implement as **page component methods/lifecycle hooks** (see table below). |
| **Per-control hooks**: `<controlSettings>.jsCode: [{ appendTo, code }]` | run at named control events | Implement as **the control component's event handlers** (see §7). |

**Top-level page lifecycle hooks — `EDBMasterConfigAppendTo` — and their available variables. Translate each to the natural Angular equivalent:**

| `appendTo` | Available variables in scope | Angular equivalent |
|---|---|---|
| `oncePageLoad` | (NONE — no `config`, no `globalData`) | run once early in `ngOnInit` before context is ready |
| `oncePageLoadWithContext` | `config`, `globalData`, `utils`, `queryParams` | run once in `ngOnInit` after config/parent context resolved |
| `gridRender` | `gridEvent`, `gridData`, `globalData`, `utils`, `queryParams` | hook that runs **every** time grid data loads; may mutate `gridData` rows; may return a `Promise` (await before render) |
| `modifyGridRequest` | `gridEvent`, `reqBody` (`IQueryFormat`), `globalData`, `utils`, `queryParams`; also `moment`, `_` available | mutate the find/sort/select request body before the grid data fetch |
| `onceGridDataLoaded` | (grid data context) | run after grid data first loads |
| `preSaveAndEdit` | `reqBody`, `globalData`, `utils`, `queryParams`, **`mode` (`'save' \| 'edit' \| 'import'`)** | run before save/update/import; **branch on `mode`**; may delete fields from `reqBody` |
| `beforeSaveModalOpen` | `formData`, `utils` (form-introspection) | mutate `formData` before the **Add** dialog opens (e.g. `formData.remarkDate = new Date()`) |
| `beforeEditModalOpen` | `formData`, `utils` | mutate `formData` before the **Edit** dialog opens |
| `beforeViewModalOpen` | `formData`, `utils` | mutate `formData` before the **View** dialog opens |
| `columnSelectionChanged` | selected columns | react to column-selector change |
| `deleteConfirmationDialogGenerator` / `deleteAllConfirmationDialogGenerator` / `editConfirmationDialogGenerator` / `saveConfirmationDialogGenerator` | the relevant item(s) | produce the confirmation message/dialog content |
| `processDataBeforeExport` | export rows | transform rows before CSV export |
| `processDataBeforePDFExport` | export rows (mutable copy) + `jsPdfDoc` (pre-created jsPDF instance, replaceable) + `globalData`, `utils`, `queryParams` | transform rows / build the PDF before PDF export (see §10.2) |

`utils` helpers exposed to hooks/scripts — re-implement on a `utils` object passed into every hook:

- `utils.findFormElement(pathOrHiddenId)` — returns the field config by its `path` **or** its `hiddenId`.
- `utils.getFlatArrayOfAllFormFields()` — flat array of all form fields **excluding** grid fields.
- `utils.getFlatArrayOfAllFormFieldsIncludingGridFields()` — flat array **including** nested grid fields.
- `utils.messageService.showInfoToast(msg, opts)` / `showSuccessToast(msg, {life})` / `showErrorToast(...)` — toast notifications.
- `utils.errorsMap[path] = 'message'` — set a validation error on another field by path (reflect onto that reactive control's errors).
- `window.top.postMessage({ eventType, eventData }, '*')` — send a custom event to the parent (see §12).

`globalData` is the object pushed by the parent via `SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT`; pass it into every hook scope. `queryParams` are the URL query params.

---

## 5. Top-level grid configuration → PrimeNG `p-table`

Build a reusable `<um-grid>` around `p-table`. Map **every** key below. Defaults are stated; apply them when the key is absent.

### 5.1 Grid-level keys

| UI Maker key | Type / default | PrimeNG / Angular equivalent | Notes |
|---|---|---|---|
| `grid.header` | string | grid title/caption header | |
| `grid.minWidth` | default `'50rem'` | `[style]="{'min-width': minWidth}"` on `<p-table>` | |
| `grid.breakpoint` | default `'40rem'` | `[breakpoint]` (responsive) | |
| `grid.currentPageReportTemplate` | e.g. `'Showing {first} to {last} of {totalRecords} entries'` | `[currentPageReportTemplate]` + `paginator` report | |
| `grid.showColumnSelector` | default `false` | `p-multiSelect` of columns toggling visible columns | |
| `grid.dataFieldPath` | string | **nested grid only**: pick the data array from this field path | not for top-level grid |
| `grid.selectFieldsForQuery` | projection `{field:1\|-1}` (default all DB fields) | API Maker query `select`; pass into the data-fetch request | **not for nested grids** |
| `grid.sortMode` | `'single'\|'multiple'`, default `'single'` | `[sortMode]` | `'multiple'` requires the meta key when adding columns |
| `grid.multiSortMeta` | `[{field, order:1\|-1}]` | `[multiSortMeta]` | each `field` must have `enableSorting` |

### 5.2 Columns — `grid.fields[]` (`IDBMasterConfigGridField`)

If `grid.fields` is empty/omitted: **auto-generate columns from the schema in schema order, EXCLUDING the Mongo `_id` column.**

| Per-column key | Equivalent | Notes |
|---|---|---|
| `header` | `<th>` text | |
| `path` | cell value path; supports dotted (`country.name`) | resolve nested |
| `enableSorting` | `pSortableColumn="path"` + `<p-sortIcon>` | |
| `visibilityStatus` | `EDBMasterConfigGridColumnVisibilityStatus.INVISIBLE \| VISIBLE` | initial column visibility (used with column selector) |
| `autoFormatDate` | format the cell as a date | default format `dd-MM-yyyy hh:mm:ss a` (date-fns); see §13 |
| `dateTimeFormat` | per-column date format override | date-fns |
| `cssClass` | CSS class on the cell/column | |
| `dbData` (FK lookup) | `{ collection, pkField, displayField }` | resolve the stored key (e.g. `_id`) to a display value via a lookup; **in nested-grid columns** use `dataSource:'db_data'` + `dbData:{pkField, displayField}` |
| `dataSource` | `'db_data'` in nested-grid columns | resolve FK display |

### 5.3 Pagination — `grid.pagination`

| Key | Default | Equivalent |
|---|---|---|
| `showPagination` | `false` | `[paginator]` |
| `serverSidePagination` | `false` | `[lazy]="true"` + `(onLazyLoad)` server fetch; **NOT supported for nested grids** |
| `rowsPerPage` | `10` | `[rows]` |
| `rowsPerPageOptions` | `[10,20,50,100,500,1000]` | `[rowsPerPageOptions]` |

### 5.4 Selection — `grid.selection`

| Key | Default | Equivalent |
|---|---|---|
| `mode` | `'multiple'` | single → `selectionMode="single"`; multiple → `"multiple"` |
| `dataKey` | auto from schema (PK) | `[dataKey]` |
| `metaKeySelection` | `true` | `[metaKeySelection]` (true = needs Ctrl/Cmd) |
| `checkboxSelection` | `false` (multiple only) | `<p-tableHeaderCheckbox>` + `<p-tableCheckbox>` column |

### 5.5 Filter / search — `grid.filter`

| Key | Default | Equivalent |
|---|---|---|
| `showGlobalSearch` | `false` | global filter input |
| `globalSearchFields` | `string[]` (dotted paths ok) | string → **contains**, number → **exact match**; pass to `[globalFilterFields]` and/or server query |
| `rowFilter` | `false` | **per-column row filter** — see §5.5.1 |

Global search (the toolbar single-box search) is detailed in §5.10.1. Per-column **row filtering** is detailed below in §5.5.1.

#### 5.5.1 Row filter — a filter for EVERY column (`grid.filter.rowFilter`)

When `grid.filter.rowFilter` is `true`, render a **filter control under (or in) every data column's header** — i.e. a filter row across the whole grid — so the user can filter each column independently. This is in addition to, and independent of, the global search box.

Implement with PrimeNG `<p-columnFilter>` per column (or a dedicated filter row via `<ng-template pTemplate="filter">`), one for **every** grid column in `grid.fields[]`:
- **Choose the filter input/match modes by the column's data type** (the same calculated `filterType` UI Maker derives per column — `EDBMasterConfigFilterType`):
  - **String/text** → text input; match modes `contains` (default), `startsWith`, `endsWith`, `equals`, `notEquals`.
  - **Number** → numeric input; match modes `equals` (default), `notEquals`, `lt`, `lte`, `gt`, `gte`.
  - **Date** → date picker; date-comparison match modes (`dateIs`, `before`, `after`, range) honoring the column's `dateTimeFormat`/`dateTimeZone` (§13).
  - **Boolean** → tri-state checkbox / true-false dropdown.
  - **FK / `dbData` lookup columns** → filter on the resolved display value (or a dropdown of the related options) — match the original behavior.
- Support **dotted paths** (e.g. `country.name`) as filter fields, same as the grid columns.
- Combine multiple active column filters with **AND** (a row must satisfy all active per-column filters), and combine with the global search where both are active.
- **Client-side grid** → use `<p-columnFilter>`'s built-in filtering against `[value]`.
- **Server-side grid** (`serverSidePagination`/`[lazy]`) → translate the active column filters into the API Maker query `find` (per-field conditions by match mode) on each `(onLazyLoad)` and re-fetch; run the `modifyGridRequest` hook so custom code can adjust the filter query.
- Provide clear/reset affordances per column (and respect any column that should be excluded from filtering, e.g. the action column and checkbox-selection column).

> Do not skip any column: if `rowFilter` is enabled, **every** displayable data column must have its own filter. The action and selection columns are not filterable.

### 5.6 Grouping — `grid.grouping` (rowGroupMode `'subheader'` only)

`groupRowsBy` → `[rowGroupMode]="'subheader'"` + `[groupRowsBy]`; `headerStyle`/`headerCssClass` (group header `<tr>`); `footerStyle`/`footerCssClass`; `footerColumnPath` (field used to render the group footer). **`rowspan` grouping is not supported.**

### 5.7 Row operations — `grid.operations`

| Key | Default | Equivalent / behavior |
|---|---|---|
| `delete` | `{ enable, cssClass, apiCallOverrides, apiCallOverridesDeleteMany }` | row delete button (icon styled by `cssClass`, e.g. `text-danger`) |
| `edit` | `{ enable, cssClass, apiCallOverrides, apiCallOverridesGetById }` | row edit button (e.g. `text-info`); edit fetches single record via `apiCallOverridesGetById` |
| `hideDeleteConfirmation` / `hideEditConfirmation` | | suppress per-row confirm dialog |
| `hideDeleteManySuccessMessage` | `false` | suppress bulk-delete success toast (then you must show it manually) |
| `disableDoubleClickForEdit` | (default: double-click row opens edit) | when set, disable double-click-to-edit |
| `disableDeleteButtonClickForDelete` | (default: delete button deletes) | when set, delete button does not delete |
| `gridOperationsOrder` | `EDBMasterGridOperationsOrder[]` = `delete \| edit \| custom` (only these 3) | order of the row action buttons |
| `gridColumnsOrder` | `EDBMasterGridColumnsOrder[]` = `checkbox \| dbFields \| actions` (only these 3) | order of column groups |
| `actionColumnWidth` | e.g. `'120px'` | width of the actions column |
| `customActionButtons[]` | `{ actionName, cssClass (icon e.g. 'pi pi-check'), style (e.g. {color:'yellow'}), confirmationMessageScript }` | per-row custom button. `confirmationMessageScript` is a **JS template-literal string** evaluated with `selectedGridItems` in scope, e.g. `` "`Do you want to approve ${selectedGridItems.name}?`" `` |

**Per-row action hiding (magic flags on the ROW DATA object)** — when rendering each row's buttons, honor:
`____hide_delete_button`, `____hide_edit_button`, `____hide_view_button`, and `____hide_<actionName>` (custom) — if `true` on the row object, hide that button for that row only.

### 5.8 Toolbar operations — top-level `operations`

| Key | Default | Equivalent |
|---|---|---|
| `operations.add` | `IDBMasterConfigOperation { enable, label, apiCallOverrides }` | Add button → opens add form dialog; title `'Add ${screenName}'` |
| `operations.hideAddConfirmation` | | suppress add confirm |
| `operations.hideDeleteAllButton` | `false` | hide Delete-All button |
| `operations.hideDeleteAllConfirmation` | | suppress delete-all confirm |
| `operations.showRefreshButton` | | Refresh button (reload grid) |
| `operations.exportData` | see §10 | Export button |
| `operations.importData` | see §10 | Import button |
| `operations.customActionButtons[]` | `IDBMasterTopLevelCustomActionButton[]` | toolbar custom buttons (distinct from row ones) |
| `operations.operationsOrder` | `EDBMasterOperationsOrder[]` | order toolbar items; **default order**: `theme_selector, column_selector, global_search, import, export, refresh, add, custom_actions, delete_all` |

### 5.9 Grid data fetch override

`grid.apiCallOverrides` (`IDBMasterAPICallOverrides`) overrides the **get-grid-data** API (see §9). Note: in the grid (non-form) context, `formData` and `allDropdownDataMap` are **not** available to the override hooks.

> `IDBMasterConfigGridField`, `IDBMasterConfigOperation`, `IDBMasterCustomActionButton`, and `IDBMasterTopLevelCustomActionButton` are not given standalone interface blocks in the docs — **infer their exact shapes from the page's actual config** and the worked examples here.

### 5.10 Global search & column selector (toolbar features — implement both fully)

These are two **toolbar** features and must each be reproduced as a first-class control in the grid header, positioned according to `operations.operationsOrder` (defaults: `column_selector` then `global_search`, near the front). Both can also be hidden/reordered like any other toolbar op.

#### 5.10.1 Global search

| Config | Type / default | Behavior to reproduce |
|---|---|---|
| `grid.filter.showGlobalSearch` | `boolean`, default `false` | When `true`, render a single search input in the toolbar that filters the grid across multiple fields at once. When `false`, render no global search box. |
| `grid.filter.globalSearchFields` | `string[]` | The exact set of fields the search applies to. Supports **dotted paths** (e.g. `'country.name'`). Only **string** and **number** fields are searchable. |

Search semantics — match the original exactly:
- **String fields** → **contains** match (case-insensitive substring).
- **Number fields** → **exact match** (equals the parsed number).
- The single search term is applied across **all** `globalSearchFields` with **OR** semantics (a row matches if any listed field matches).
- **Client-side grid** (`serverSidePagination` false): bind `[globalFilterFields]="globalSearchFields"` on `<p-table>` and call `dt.filterGlobal(term, 'contains')`; because PrimeNG's single global mode is contains-only, additionally enforce the **number-exact** rule yourself (custom filter/predicate) so numeric columns are not matched as substrings.
- **Server-side grid** (`serverSidePagination` true / `[lazy]`): on each keystroke (debounced), build the API Maker query `find` with an `$or` of per-field conditions — string fields as `contains`/regex, number fields as exact equality — over `globalSearchFields`, and re-fetch via the data service. Run any `modifyGridRequest` hook so custom code can adjust the search query.
- Debounce input (e.g. 300 ms); show a clear (`×`) affordance that resets the search and reloads the grid.

#### 5.10.2 Column selector

| Config | Type / default | Behavior to reproduce |
|---|---|---|
| `grid.showColumnSelector` | `boolean`, default `false` | When `true`, render a column-selector control in the toolbar (a `p-multiSelect` of the grid columns, or equivalent overlay of checkboxes) that lets the user toggle which columns are visible. |
| per-column `visibilityStatus` | `EDBMasterConfigGridColumnVisibilityStatus.VISIBLE \| INVISIBLE` | Sets each column's **initial** checked/visible state in the selector. Columns marked `INVISIBLE` start hidden but remain available to toggle on. Absent → visible by default. |

Implementation:
- Build the selector's option list from `grid.fields[]` (label = column `header`, value = the column). Seed the selected/visible set from each column's `visibilityStatus` (everything except `INVISIBLE`).
- Bind the table body/header column rendering to the currently-selected columns so toggling immediately shows/hides columns. Keep the **action column** and the **checkbox-selection column** out of (or fixed in) the selector as the original does — they are governed by `gridColumnsOrder`, not the column selector.
- Position the selector per `operations.operationsOrder` (default places `column_selector` first). Hiding it entirely = `showColumnSelector: false` (or omit it from `operationsOrder`).
- When the selection changes, fire the page-level **`columnSelectionChanged`** lifecycle hook (`EDBMasterConfigAppendTo.columnSelectionChanged`, see §4.2) with the selected columns so custom code can react.
- Optional: persist the chosen columns for the page session if the original does (otherwise reset to `visibilityStatus` defaults on reload).

---

## 6. Form framework → Angular Reactive Forms

### 6.1 Layout

`form.fields` is a **2-D array** (outer = rows, inner = columns). A field that itself has a nested `fields` (also 2-D) and **no `control`** is a pure **layout container** — render its children but bind no value for it. `form.width` sets the dialog (`p-dialog`) width. The form runs in three modes: **add**, **edit**, **view** (view = read-only).

### 6.2 Shared base field props (`IDBMasterConfigFormField`) — present on EVERY control

| Prop | Type / default | Handling |
|---|---|---|
| `hiddenId` | string | element id for programmatic access (`utils.findFormElement`) |
| `label` | string | field label (may be `' '` to reserve space for alignment) |
| `helpText` | HTML string | render **below** the control via `[innerHTML]` (sanitized) |
| `path` | dotted string | the nested `FormControl` key where the value is stored |
| `control` | `EDBMasterFormControl` | which control component to render |
| `cssClassDiv` | default `col-lg mt-4 col-md-{calc by columns.length}` | wrapper div classes |
| `autofocus` | default `false` | focus on form open |
| `disabled` | `boolean \| JS-expression string` | static OR evaluated expression (see §6.3) |
| `visible` | `boolean \| JS-expression string`, default `true` | static OR evaluated expression |
| `fields` | `IDBMasterConfigFormField[][]` | nested 2-D layout |
| `validations` | `Pick<IPropertyValidation,'required'> & { requiredFun?: string }` | `required` (boolean) + `requiredFun` (dynamic expression) |
| `validationErrors.required` | string | custom required error message |

Build the `FormGroup` from all field `path`s (nested groups for dotted paths). Add/remove `Validators.required` per `validations.required`. Set `[autofocus]`. Render `helpText` HTML below each control. **Container fields and value-less controls (button, image, customHTML, divider, accordion, tab_view) must NOT be registered as value-bearing FormControls** unless they actually carry a `path`.

### 6.3 JS-expression-string evaluation (`visible` / `disabled` / `requiredFun`)

`visible`, `disabled`, and `requiredFun` may each be a **boolean** OR a **JavaScript expression string** evaluated against form data, e.g. `"formData.type === 'readonly'"`, `"formData.userRole === 'admin'"`, `"formData.type === 'individual' ? true : false"`.

- Implement a single **`expression.service.ts`** evaluator. Build the expression scope: `{ formData, queryParams, globalData, utils, column, config }`.
- Recommended safe approach: a constrained `new Function('formData','queryParams','globalData','utils','column','config', 'return (' + expr + ');')` invoked with those args, wrapped in try/catch. Do **not** use `eval`. (If your security posture forbids `new Function`, swap in a sandbox like a small expression interpreter, but the **string contract must be honored**.)
- **Re-evaluate reactively** on `form.valueChanges` (and on `globalData` changes). Bind `visible` → `*ngIf` / `[hidden]`; `disabled` → `control.disable()/enable()` (and `[disabled]` for non-control elements).
- **`requiredFun` takes precedence over static `required`** when both are present: when `requiredFun` exists, ignore static `required` and dynamically add/remove `Validators.required` based on the evaluated result on every value change.
- The full `IPropertyValidation` set exists at schema level (`required, min, max, minLength, maxLength, unique [deprecated], email, validatorFun, enum`). Most controls only expose `required`/`requiredFun` via `validations`, but **control-specific** validators appear in settings (e.g. `inputTextSettings.minLength`, `inputNumberSettings.min/max/minLength`, `editorSettings.maxLength`). Map each to the matching Angular `Validators.*`; map a custom `validatorFun` to a custom validator; map `enum` to a membership validator.

---

## 7. Per-control conversion reference (THE BIG SECTION — all 24 controls)

For **every** control: render the listed PrimeNG component bound to a `FormControl` at `path` (support dotted paths); honor the **shared base props** (§6.2); apply `style` as `[ngStyle]`, `cssClass` as `styleClass`, `tooltip`/`tooltipPosition`/`tooltipStyleClass` via `pTooltip`; wire the control's `jsCode` hooks to the matching PrimeNG outputs. The `jsCode` `$scope` (`IDBMasterUIPageUtilsScope`) for input-like controls is `{ formData, column, allDropdownDataMap, globalData, utils, queryParams, config, event }` (data-source controls additionally get `reqBody`, `dropdownData`, `reloadDropdownsOfPath`). Code may be a **string, a function, or a `userUtils.*` reference**, may **mutate data**, **return a `Promise` (await)**, or **return a function (invoke)**.

### 7.1 input — `p-inputText` (`pInputText` on `<input>`)
- **Settings (`inputTextSettings`)**: `style`, `placeholder`, `autocomplete` (default `off`), `spellcheck` (default `false`), `maxLength` (→ `maxlength` attr + `Validators.maxLength`), `minLength` (→ `Validators.minLength`), `tooltip`/`tooltipPosition`/`tooltipStyleClass`, `jsCode`, `validationErrors.minLength` (custom minLength message).
- **Validation**: `required`/`requiredFun`; `minLength` (custom msg via `inputTextSettings.validationErrors.minLength`); `maxLength`.
- **Events (`EDBMasterInputTextAppendTo`)**: `visible`, `disabled`, `ngModelChange`, `focus`, `blur`, `keyUp`, `keyDown`.

### 7.2 textarea — `pInputTextarea` on `<textarea>`
- **Settings (`textAreaSettings`)**: `style`, `placeholder`, `rows`, `autoResize` (default `false`), `maxLength`, `tooltip*`, `jsCode`.
- **Validation**: `required`/`requiredFun`; `maxLength`. (No minLength/email/pattern exposed.)
- **Events (`EDBMasterTextAreaAppendTo`)**: `visible`, `disabled`, `ngModelChange`, `focus`, `blur`, `keyUp`, `keyDown`.

### 7.3 password — `p-password`
- **Settings (`inputPasswordSettings`)**: `style`, `cssClass`, `placeholder`, `maxLength`, `tooltip*`, `jsCode`. (`toggleMask`, `feedback`, prompt/strength labels referenced in examples — map to `p-password` `[toggleMask]`/`[feedback]`/labels.)
- **Validation**: `required`/`requiredFun`; `maxLength`.
- **Events (`EDBMasterInputPasswordAppendTo`)**: `visible`, `disabled`, `ngModelChange`, `focus`, `blur`, `keyUp`, `keyDown`.

### 7.4 inputNumber — `p-inputNumber`
- **Settings (`inputNumberSettings`)**: `style`, `cssClass`, `placeholder`, `min`, `max`, `minFractionDigits`, `mode` (`'decimal'\|'currency'\|''`), `currency` (ISO 4217 — **required when `mode='currency'`**, no default), `locale` (default `'en-US'`), `prefix`, `suffix`, `showButtons`, `buttonLayout` (default `'stacked'`; also `horizontal`/`vertical`), `step`, `useGrouping`, `maxLength`, `minLength`, `tooltip*`, `jsCode`, `validationErrors.minLength`.
- **Validation**: `required`/`requiredFun`; `min`/`max` (PrimeNG constraint); `minLength`/`maxLength`; `minFractionDigits`.
- **Events (`EDBMasterInputNumberAppendTo`)**: `visible`, `disabled`, `ngModelChange`, `focus`, `blur`, `keyUp`, `keyDown`.

### 7.5 inputMask — `p-inputMask`
- **Settings (`inputMaskSettings`)**: `style`, `cssClass`, `mask` (`a`=alpha, `9`=numeric, `*`=alphanumeric, `?`=optional remainder, literals `( ) - / . , :`; e.g. `'(999) 999-9999'`, `'99/99/9999'`, `'99:99 aa'`), `placeholder`, `slotChar` (default `_`), `autoClear` (default `true` — clears incomplete value on blur), `maxLength`, `tooltip*`, `jsCode`.
- **Validation**: `required`/`requiredFun`; `maxLength`; mask enforces format/length; `autoClear` enforces completeness.
- **Events (`EDBMasterInputMaskAppendTo`)**: `visible`, `disabled`, `ngModelChange`, `focus`, `blur`, `complete`, `keyUp`, `keyDown`.
- **GOTCHA**: modifying the model value inside the **`blur`** hook does **NOT** work (PrimeNG bug) — route model-mutating logic to the **`complete`** hook.

### 7.6 inputOtp — `p-inputOtp`
- **Settings (`inputOtpSettings`)**: `mask` (boolean — obscure values, password-style), `integerOnly` (numeric only), `uiControlWidth` (default `300px`), `length` (number of boxes / OTP length), `tooltip*`.
- **Validation**: `required`/`requiredFun`; `integerOnly`; `length` (fixed OTP length).
- **Events**: none control-specific; only evaluation-based `visible`/`disabled`/`requiredFun`. Auto-focus between boxes is built-in.

### 7.7 date_picker — `p-calendar` (Calendar / DatePicker)
- Value is always a native **JS `Date`** stored at `path` (write back a `Date`, not ISO string).
- **Settings (`datePickerSettings`)**: `style`, `cssClass`, `placeholder`, `showSeconds`, `showTime`, `hourFormat` (`'12'\|'24'`, default `'24'`), `dateTimeFormat`, `timeOnly`, `minDate` (`Date`), `maxDate` (`Date`), `view` (`'date'\|'month'\|'year'`, default `'date'`), `tooltip*`, `jsCode`.
- **Mapping**: `showTime`→`[showTime]`, `showSeconds`→`[showSeconds]`, `hourFormat`→`[hourFormat]`, `timeOnly`→`[timeOnly]`, `minDate`/`maxDate`→`[minDate]`/`[maxDate]`, `view`→`[view]`, `dateTimeFormat`→`[dateFormat]` (convert date-fns tokens to PrimeNG calendar tokens, e.g. `dd-MM-yyyy` → `dd-mm-yy`; see §13).
- **GOTCHA**: for `view='month'` set `dateTimeFormat='MM-yyyy'`; for `view='year'` set `'yyyy'`. `minDate`/`maxDate` are selection constraints only — add equivalent validators if true validation is needed.
- **Validation**: `required`/`requiredFun`.
- **Events (`EDBMasterDatePickerAppendTo`)**: `visible`, `disabled`, `ngModelChange` (on `onSelect`/value change), `focus`, `blur`.

### 7.8 checkbox — `p-checkbox` (binary)
- Bind a **boolean** FormControl; `[binary]="true"`.
- **Settings (`checkboxSettings`)**: `tooltip*`, `jsCode` (`onChange`).
- **Validation**: `required`/`requiredFun`.
- **Events (`EDBMasterCheckboxAppendTo`)**: `ngModelChange` (a.k.a. onChange → `(onChange)`), `visible`, `disabled`.

### 7.9 radio — `p-radioButton` (one per option, single `FormControl`)
- **Settings (`radioSettings`)**: `displayType` (`'inline'` = horizontal one line, `'new_line'` = vertical), `displayInCenter` (center the group), **`options[]` (REQUIRED)**: `{ label, value }` (value any type: string/boolean/number — selected option's `value` is stored), `tooltip*`, `jsCode`.
- **Validation**: `required`/`requiredFun`.
- **Events (`EDBMasterRadioAppendTo`)**: `ngModelChange`, `visible`, `disabled`.

### 7.10 dropdown — `p-dropdown`  *(data-source control — see §8)*
- **Settings (`dropdownSettings`)**: `style`, `cssClass`, `placeholder`, `showClear` (default `false`), **`dataSource` (REQUIRED: `'static_data'\|'db_data'\|'api_call'`)**, `staticData[]`, `dbData` (`Partial<{instance,database,collection,table}> & {find,select,limit,deep,sort}` — `select` may be CSV string OR object map `{field:1}`; identity may be inherited from schema), `optionLabel` (default `'label'`, **HTML supported** → use item template with `[innerHTML]`), `optionValue` (default `'value'`, stored value), `filter` (default `false`), `filterBy` (comma-separated, no spaces), `filterMatchMode` (default `'contains'`; also `startsWith\|endsWith\|equals\|notEquals\|in\|lt\|lte\|gt\|gte`), `alwaysGetLatestDataOnFormOpen` (default `false`), `virtualScroll` (default `false`), `reloadDropdownsOfPath[]`, `isDependentOnPath[]`, `tooltip*`, `jsCode`, `addNewFormConfig` (`IDBMasterConfig` — inline add modal), `apiCallOverrides`.
- **Validation**: `required`/`requiredFun`.
- **Events (`EDBMasterDropdownAppendTo`)**: `visible`, `disabled`, `modifyDropdownRequest` (mutate `reqBody` before fetch), `onceDropdownDataLoaded` (post-process `dropdownData`), `onChange`.

### 7.11 auto_complete — `p-autoComplete`  *(data-source control — see §8)*  (v1.11+)
- Stores the selected option's **`optionLabel` value** (there is **NO `optionValue`** — it is commented out; the whole label-field value is stored).
- **Settings (`autocompleteSettings`)**: `style`, `cssClass`, `placeholder`, `showClear`, `minLengthForSearch` (→ `minLength`), `delay` (debounce ms), `forceSelection` (clears manual input not matching a suggestion), **`dataSource`**, `staticData[]`, `dbData`, `optionLabel` (default `'label'`, HTML supported), `filterBy`, `filterMatchMode` (**`contains\|startsWith\|endsWith` only**), `alwaysGetLatestDataOnFormOpen`, `virtualScroll`, `reloadDropdownsOfPath[]`, `isDependentOnPath[]`, `dropdown` (show dropdown button), `maxLength`, `tooltip*`, `addNewFormConfig`, `apiCallOverrides`, `jsCode`.
- **`completeMethod`**: fire only after `minLengthForSearch` chars, debounced by `delay`; for `db_data`/`api_call` honor `isDependentOnPath` (skip until all parent paths have values).
- **Validation**: `required`/`requiredFun`; `forceSelection`; `maxLength`.
- **Events (`EDBMasterAutoCompleteAppendTo`)**: `onSelect`, `visible`, `disabled`, `modifyAutoCompleteRequest`, `onceAutoCompleteDataLoaded`, `focus`, `blur`, `keyUp`, `keyDown`, `onShow`, `onHide`, `onClear`, `onDropdownClick`.

### 7.12 multi_select — `p-multiSelect`  *(data-source control — see §8)*
- **Settings (`multiselectSettings`)**: `style`, `cssClass`, `placeholder`, `showClear`, `minLength`, `delay`, **`dataSource`**, `staticData[]`, `dbData`, `optionLabel` (default `'label'`, HTML), `optionValue` (default `'value'`), `showHeader`, `filter`, `filterBy`, `filterMatchMode` (default `'contains'`; full set), `alwaysGetLatestDataOnFormOpen`, `virtualScroll`, `maxSelectedLabels`, `selectionLimit`, `selectedItemsLabel` (e.g. `'{0} items selected'`), `showToggleAll`, `optionDisabled` (field marking an option disabled), `optionGroupLabel` + `optionGroupChildren` (grouping), `display` (`'comma'\|'chip'`), `reloadDropdownsOfPath[]`, `isDependentOnPath[]`, `addNewFormConfig`, `apiCallOverrides`, `tooltip*`, `jsCode`.
- **Validation**: `required`/`requiredFun`; `selectionLimit`.
- **Events (`EDBMasterMultiSelectAppendTo`)**: `visible`, `disabled`, `modifyMultiSelectRequest`, `onceMultiSelectDataLoaded`, `onChange` (+ panel show/hide/clear as applicable).

### 7.13 color_picker — `p-colorPicker`
- **Settings (`colorPickerSettings`)**: `style`, `cssClass`, `format` (`'hex'\|'rgb'\|'hsb'`), `tooltip*`, `jsCode`.
- **Validation**: `required`/`requiredFun`.
- **Events (`EDBMasterColorPickerAppendTo`)**: `visible`, `disabled`, `ngModelChange`.

### 7.14 editor — `p-editor` (Quill WYSIWYG)  (v1.14.0+)
- Value is **HTML rich text**; round-trip and sanitize on render.
- **Settings (`editorSettings`)**: `style` (e.g. `{height:'350px'}`), `placeholder`, `maxLength` (no native maxlength — enforce via `Validators.maxLength` and/or guard in `onTextChange`), `tooltip*`, **`formats[]`** (whitelist of allowed Quill formats from: `background, bold, color, font, code, italic, link, size, strike, script, underline, blockquote, header, indent, list, align, direction, code-block, image, video, clean`; configure the Quill toolbar/formats whitelist — when omitted use full default toolbar), `jsCode`.
- **Validation**: `required`/`requiredFun`; `maxLength`.
- **Events (`EDBMasterEditorAppendTo`)**: `visible`, `disabled`, `ngModelChange`, `onTextChange`, `onInit`, `onSelectionChange`, `focus`, `blur`, `keyUp`, `keyDown`.

### 7.15 rating — `p-rating`
- **Settings (`ratingSettings`)**: `cancel` (default `true` — show clear icon), `stars` (number of stars = max value), `iconOnClass`/`iconOnStyle`, `iconOffClass`/`iconOffStyle`, `iconCancelClass`/`iconCancelStyle`, `onIconHTML`/`offIconHTML`/`cancelIconHTML` (raw custom HTML icons, e.g. `<img>` or `<i>` — override class/style), `tooltip*`, `jsCode`.
- **Validation**: `required`/`requiredFun`.
- **Events (`EDBMasterRatingAppendTo`)**: `ngModelChange`, `onRate`, `onCancel`, `onFocus`, `onBlur`, `visible`, `disabled`.

### 7.16 knob — `p-knob`
- **Settings (`knobSettings`)**: `min`, `max`, `step`, `size` (px diameter, e.g. `150`, `200`), `strokeWidth`, `valueColor`, `rangeColor`, `textColor`, `valueTemplate` (e.g. `'{value}%'`, `'{value}°C'` — `{value}` substituted), `cssClass`, `style`, `tooltip*`, `jsCode`.
- **Validation**: `required`/`requiredFun`; `min`/`max`/`step` enforced by knob.
- **Events (`EDBMasterKnobAppendTo`)**: `ngModelChange`, `visible`, `disabled`.

### 7.17 file_upload — `p-fileUpload`  (full spec)
- **Stored value** at `path` = **array of `{ originalName: string, uploadPath: string }`** objects (extra props allowed). NOT raw `File` objects.
- **Settings (`fileUploadSettings`)**: `style`, `cssClass`, `auto` (default `false`; `true` auto-uploads after selection, else show upload button), **`uploadApiUrl` (REQUIRED)**, `downloadApiUrl`, `removeApiUrl`, `multiple`, `accept` (e.g. `'image/*'`, `'.pdf,.doc,.docx'`), `maxFileSize` (default `10000000` = 10MB), `fileLimit`, `tooltip*`. (`_showUploadButton`, `_fileSelectEvent` are **internal** — do not expose; you may need equivalent internal state.)
- **Contracts** (resolve `:beHostPort` / `:userPath` placeholders first — see §9):
  - **UPLOAD**: POST files in multipart field named exactly **`files`** to `uploadApiUrl`; response is `[{originalName, uploadPath}, ...]` → becomes the FormControl value. Use `p-fileUpload` `customUpload`/`uploadHandler`.
  - **DOWNLOAD/PREVIEW**: call `downloadApiUrl` → returns **base64**; use for inline preview (data URI for images) and download.
  - **REMOVE**: call `removeApiUrl` with a single `{ originalName, uploadPath }` payload, then update the FormControl array.
- **Validation**: `required`/`requiredFun`; `maxFileSize`; `fileLimit`; `accept`.
- No data-source / cascading behavior.

### 7.18 image — `p-image` (display-only; `<img>` inside `<span>`)
- **Settings (`imageSettings`)**: **`src` (REQUIRED; may start empty and be set dynamically via jsCode from `formData`, e.g. `column.imageSettings.src = formData.profilePicture`)**, `style`, `cssClass`, `imageParentSpanClass` (class on the wrapping `<span>`), `width`, `height`, `alt`, `preview` (fullscreen preview on click), `previewImageSrc` (separate high-res preview source, distinct from thumbnail `src`), `tooltip*`, `jsCode`.
- Display-only — do NOT register as a value FormControl unless a `path` is set. Often paired with `file_upload` to preview uploaded images. Re-render when `src` changes.
- **Events (`EDBMasterImageAppendTo`)**: `visible`, `disabled`, `click`.

### 7.19 button — `p-button` (action only, no value)
- Render as a standalone `<p-button>`; do **NOT** add it as a value-bearing FormControl.
- **Settings (`buttonSettings`)**: `label` (required, can be empty for icon-only), `style`, `cssClass`, `link`, `icon`, `iconPos` (default `'left'`; `right/top/bottom`), `loading`, `loadingIcon`, `severity` (`primary/success/info/warning/danger`), `raised`, `rounded`, `text`, `outlined`, `badge`, `badgeClass` (newer PrimeNG: `badgeSeverity`), `size` (`'small'\|'large'`), `tooltip*`, `jsCode` (`click`).
- **Events (`EDBMasterButtonAppendTo`)**: `click` (→ `(onClick)`), `visible`, `disabled`. Click code commonly uses `utils.messageService.showSuccessToast(...)` and `utils.errorsMap[path]='message'`.

### 7.20 divider — `p-divider` (presentational, no value)
- **Settings (`dividerSettings`)**: `style`, `cssClass`, `align` (`center/left/right` for horizontal; `top/center/bottom` for vertical → `[align]`), `type` (`dashed/dotted/solid` → `[type]`), `dividerText` (projected content; or use field `label`).
- No control-specific events; only `visible`/`disabled` (evaluated).

### 7.21 customHTML — `<div [innerHTML]>` + `DomSanitizer` (no PrimeNG component)
- **Settings (`customHTMLSettings`)**: **`htmlCode` (REQUIRED)** — raw HTML rendered via `[innerHTML]`. **Sanitized** (XSS protection — some tags/attributes/event-handlers/scripts stripped). Use Angular `DomSanitizer.sanitize` (NOT `bypassSecurityTrust`) to preserve the same protective behavior. Keep inline style attributes intact where possible.
- No value, no FormControl. Primary dynamic feature is `visible`.

### 7.22 accordion — `p-accordion` (field container, no value)
- **Settings (`accordionSettings`)**: `style`, `cssClass`, `activeIndex` (`number | number[]`), `defaultIndex` (default `0`; seed `activeIndex` if absent), `multiple` (→ `[multiple]`; in multiple mode `activeIndex` is `number[]`), `jsCode`, **`tabs[]`**: each `{ style, header (HTML → render with `[innerHTML]`), headerStyle, headerCssClass, disabled (**boolean OR JS-expression string**), fields (2-D nested fields) }`.
- Recursively render each tab's `fields[][]` as their own reactive controls; the accordion contributes no value of its own.
- **Events (`EDBMasterAccordionAppendTo`)**: `visible`, `disabled`, `activeIndexChange`.

### 7.23 tab_view — `p-tabView` + `p-tabPanel` (field container, no value)
- **Settings (`tabViewSettings`)**: `activeIndex`, `defaultIndex` (default `0`), `style`, `cssClass`, `jsCode`, **`tabs[]`**: `{ style, header (HTML), headerStyle, headerCssClass, disabled (**boolean only** here), fields (2-D nested fields) }`.
- **Events (`EDBMasterTabViewAppendTo`)**: `visible`, `disabled`, `activeIndexChange` (→ `(onChange)`/`(activeIndexChange)`).

### 7.24 grid (nested) — `p-table` + add/edit `p-dialog` backed by a `FormArray` of `FormGroup`s
- Value is an **array** stored at `path` → model as a `FormArray<FormGroup>`; bind to `<p-table>` `[value]`; edit/add via a `p-dialog` hosting the nested form.
- **Settings (`gridSettings`)** = `Omit<IDBMasterConfig,'schema'>` — a full child config (its own `operations`, `grid`, `form`, `jsCode`, `externalLibs`, `cssCode`, `apiCallOverrides`...). **Recurse**: a nested grid inside `gridSettings.form` is another `FormArray` + `p-table` + dialog. **Unlimited nesting.** For nested grids, locate the child array via `dataFieldPath`; **`serverSidePagination` and `selectFieldsForQuery` are NOT supported for nested grids.**
- Reproduce all grid features (§5), row magic-hide flags, the Add modal flow (`operations.add.enable/label`; on save push the FormGroup value into the FormArray; respect `hideAddConfirmation`), edit (open dialog with row), delete (remove from array; respect confirmations/`disableDoubleClickForEdit`/`disableDeleteButtonClickForDelete`), `db_data` column FK resolution (collection/pkField/displayField → display value), and nested-form dropdown loading (re-fetch on dialog open when `alwaysGetLatestDataOnFormOpen`).
- **Lifecycle hooks (`gridSettings.jsCode`)**: `oncePageLoad`, `oncePageLoadWithContext`, `gridRender`, `modifyGridRequest`, `preSaveAndEdit` (mode), `beforeSaveModalOpen`, `beforeEditModalOpen` (see §4.2).
- Nested form fields carry the **full** `IPropertyValidation` (`required, min, max, minLength, maxLength, email, validatorFun, enum`) — convert each to Angular validators.

---

## 8. Dependent / cascading dropdowns

Cascading is **NOT automatic**. It requires, on the relevant `dropdownSettings`/`autocompleteSettings`/`multiselectSettings`, all three of:

1. **`reloadDropdownsOfPath: string[]`** — on the **parent** control. When the parent's value changes, the child controls at these paths are **cleared and reloaded**.
2. **`isDependentOnPath: string[]`** — on the **child** control. The child loads data **only when** all listed parent paths have values in `formData`.
3. **`jsCode` `modify{Dropdown|AutoComplete|MultiSelect}Request`** — on the child. Mutates `reqBody` to inject the parent value into the query, e.g. `` reqBody.find.continentId = formData.continentId; ``.

**N-level example: planet → continent → country → state**

```jsonc
// continent depends on planet, reloads country on change
{ path:'continentId', control:'dropdown', dropdownSettings:{
  dataSource:'db_data', dbData:{ collection:'ui_maker_continent' }, optionValue:'_id', optionLabel:'name',
  isDependentOnPath:['planetId'], reloadDropdownsOfPath:['countryId'],
  jsCode:[{ appendTo:'modifyDropdownRequest', code:`reqBody.find.planetId = formData.planetId;` }]
}}
```

**Angular re-implementation:**

- For each control with `reloadDropdownsOfPath`, subscribe to its FormControl `valueChanges`. On change: for each child path, **reset the child's value** and **trigger its reload** (clear cached options and re-fetch).
- For each control with `isDependentOnPath`, gate `loadOptions()` behind a check that every listed parent path has a non-empty value in `formData`; otherwise leave options empty/disabled.
- Before each child fetch, run the `modify*Request` hook against a mutable `reqBody` object so it can inject parent values (`reqBody.find.<parent> = formData.<parent>`), then send that body to the data service.
- Run `once*DataLoaded` after the fetch to post-process `dropdownData`.
- Cascading mixes freely across `dropdown` / `auto_complete` / `multi_select`. The chain can be arbitrarily deep — reset and reload **transitively** down the chain. `reloadDropdownsOfPath` is also available as a mutable variable inside hooks: pushing a path into it triggers that path's reload.

---

## 9. API layer & overrides

Replace API Maker's auto-generated calls and all `apiCallOverrides` with an explicit `api.service.ts` (`HttpClient`).

**`IDBMasterAPICallOverrides`:**

```ts
interface IDBMasterAPICallOverrides {
  url: string;                 // REQUIRED; supports :beHostPort and :userPath tokens
  headers?: any;               // custom request headers
  method?: 'POST';             // ONLY 'POST' is supported
  body?: any;                  // custom request body
  pkField?: string;            // PK field name; '_id' for MongoDB (delete/getById/deleteMany)
  codeBeforeAPICall?: string;  // JS hook: mutate request before call
  codeAfterAPICall?: string;   // JS hook: mutate response after call
}
```

- **All override calls are POST.** Implement only POST.
- **Placeholder substitution** (do before every call): `:beHostPort` → backend protocol+host+port (e.g. `https://example.com`); `:userPath` → the admin user path segment.
- **`codeBeforeAPICall` / `codeAfterAPICall` scope**: `config`, `formData`, `allDropdownDataMap`, `apiResponse` (after only), `globalData`, `headers`, `reqBody`, `utils`, `queryParams`, `mode`. **Note**: in the **grid** context, `formData` and `allDropdownDataMap` are NOT available.

**Per-operation overrides to implement (each points to a custom endpoint):**

| Override path | Operation | Extra |
|---|---|---|
| `operations.add.apiCallOverrides` | create/save | |
| `operations.importData.apiCallOverrides` | CSV import persist | default uses save API |
| `grid.apiCallOverrides` | get grid data | |
| `grid.operations.delete.apiCallOverrides` | delete by id | uses `pkField` |
| `grid.operations.delete.apiCallOverridesDeleteMany` | bulk delete | uses `pkField` |
| `grid.operations.edit.apiCallOverrides` | edit/update by id | |
| `grid.operations.edit.apiCallOverridesGetById` | fetch single record before edit | uses `pkField` |
| `dropdownSettings/autocompleteSettings/multiselectSettings.apiCallOverrides` | dropdown options (`dataSource:'api_call'`) | map response via `optionLabel`/`optionValue` |

When no override exists, call the standard API Maker endpoint for that operation; build query bodies (`find`, `select`, `limit`, `deep`, `sort`) via `query.service.ts` using the API Maker `IQueryFormat` shape (`selectFieldsForQuery` projection `{field:1|-1}`).

---

## 10. CSV import & export

### 10.1 Import — `operations.importData` (`IDBMasterImportData`)

```ts
interface IDBMasterImportData {
  enable: boolean;
  icon?: string;                 // default 'pi pi-file-import'
  gridWidth?: string;            // import preview grid width, e.g. '2000px'
  fieldMappings: IDBMasterImportGridColumn[];
  apiCallOverrides?: IDBMasterAPICallOverrides; // default: the save API
}
interface IDBMasterImportGridColumn {
  header: string;                          // UI preview grid header
  sourceField: string;                     // CSV column name (e.g. 'NAME', 'DOB')
  targetField: string;                     // DB field path (e.g. 'name', 'dateOfBirth')
  convertTargetFieldToType: EType;         // convert CSV value to this type before save (e.g. string, date)
  dateParseFormat?: string;                // date-fns parse format (default ISO)
  dateTimeZone?: string;                   // tz to convert parsed date into before DB query
  dateFormatUIDisplay?: string;            // date format for UI display in preview
  dateTimeZoneUIDisplay?: string;          // tz for UI display in preview
  width?: string;                          // per-column preview width (e.g. '300px')
  filterType?: EDBMasterConfigFilterType;  // CALCULATED/internal — do not set
}
```

Implement: a file picker → parse CSV with **Papa Parse** (available globally as `Papa`) → render an editable preview `p-table` (columns from `fieldMappings`, widths from `width`/`gridWidth`) → for each row map `sourceField`→`targetField`, **convert to `convertTargetFieldToType`** (use date-fns `parse` with `dateParseFormat`; convert tz via date-fns-tz with `dateTimeZone`; display in preview using `dateFormatUIDisplay`/`dateTimeZoneUIDisplay`) → POST to the save API (or `apiCallOverrides`).

### 10.2 Export — `operations.exportData` (CSV **and** PDF)

The Export toolbar button offers **CSV** and **PDF** export, each supporting **export all** and **export selected rows**. Implement both formats.

```jsonc
operations: {
  exportData: {
    showExportButton: true,
    icon: 'pi pi-download',                                  // default 'pi pi-download'
    csv: { enable: true, labelCSV: 'CSV', labelCSVSelected: 'CSV Selected' },
    pdf: { enable: true, labelPDF: 'PDF', labelPDFSelected: 'PDF Selected' }
  }
}
```

| Key | Default | Behavior |
|---|---|---|
| `exportData.showExportButton` | | show the Export button/menu in the toolbar |
| `exportData.icon` | `'pi pi-download'` | export button icon |
| `exportData.csv.enable` | | enable **CSV** export |
| `exportData.csv.labelCSV` | `'CSV'` | "export all rows → CSV" label |
| `exportData.csv.labelCSVSelected` | `'CSV Selected'` | "export selected rows → CSV" label |
| `exportData.pdf.enable` | | enable **PDF** export |
| `exportData.pdf.labelPDF` | `'PDF'` | "export all rows → PDF" label |
| `exportData.pdf.labelPDFSelected` | `'PDF Selected'` | "export selected rows → PDF" label |

> The source interface explicitly defines `csv`; `pdf` is the parallel option (it appears commented in the older grid interface, and PDF export is fully wired through the `processDataBeforePDFExport` hook below). Confirm the page's actual `exportData` config and reproduce whichever of `csv`/`pdf` it enables. Keep the same key/label names so existing configs keep working.

**Both formats** must expose two actions each: **export all** (the full/queried grid data) and **export selected** (only the rows currently selected via grid selection). When nothing is selected, the "selected" action exports nothing (or is disabled) — match the original. Honor the currently **visible columns** (from the column selector, §5.10.2) and the configured grid **field order/headers** in the exported output.

**CSV export:**
- Run the **`processDataBeforeExport`** hook first (`EDBMasterConfigAppendTo.processDataBeforeExport`). Scope: `gridData` (a **mutable copy** — mutating it does NOT affect the displayed grid), `globalData`, `utils`, `queryParams`. Use it to reshape/format rows before serialization.
- Serialize the (possibly transformed) rows to CSV using **Papa Parse** (`Papa.unparse`, available globally as `Papa`) with the grid headers as the column headers, then trigger a browser download.

**PDF export:**
- Run the **`processDataBeforePDFExport`** hook first (`EDBMasterConfigAppendTo.processDataBeforePDFExport`). Scope: `gridData` (a **mutable copy**, does not affect the displayed grid), **`jsPdfDoc`**, `globalData`, `utils`, `queryParams`.
- **`jsPdfDoc`** is a **pre-created jsPDF instance** the hook may customize **or replace**. The hook can build a fully custom document, e.g.:
  ```js
  const { jsPDF } = window.jspdf;                 // jsPDF available globally as window.jspdf
  const doc = new jsPDF({ orientation: 'landscape', format: 'a4' });
  $scope.jsPdfDoc = doc;                          // replace the instance
  $scope.jsPdfDoc.text('Custom Report Title', 14, 15);
  ```
- After the hook, render the grid rows into `jsPdfDoc` (a tabular layout — typically via the jsPDF autoTable plugin or manual `text`/`table` calls) using visible columns + headers, then save/download the PDF. If the hook fully built the document, respect its content.
- Load jsPDF (and autoTable, if used) the same way other libs are loaded (§4.2 / `externalLibs`); expose it as `window.jspdf` so hook code referencing `window.jspdf` works unchanged.

**On export completion** emit the `GRID_EXPORTED` parent event (§12) for both formats.

---

## 11. Theming

- `theme` = an `EDBMasterTheme` value mapping **directly to a PrimeNG prebuilt theme stylesheet name** (e.g. `md-dark-deeppurple` → PrimeNG `md-dark-deeppurple`). Families include Bootstrap4, MD (Material), MDC, Fluent, Lara, Soho, Viva, Mira, Nano, Saga, Vela, Arya, Nova, Luna, Rhea — with light/dark variants and color accents (blue/indigo/purple/teal/green/orange/amber/pink). Load the corresponding PrimeNG theme CSS at runtime.
- `showThemeSelector` (default `false`) → render a **theme switcher** in the grid header that swaps the loaded theme stylesheet (`theme.service.ts`).
- Global defaults to carry: `dateTimeFormat` (default `'dd-MM-yyyy hh:mm:ss a'`), `dateTimeZone` (default browser tz).

---

## 12. Iframe / parent-app integration

UI Maker pages run in an iframe and communicate via `window.postMessage` with `{ eventType, eventData }`. Implement `parent-integration.service.ts` to send/receive these. **Handshake**: iframe emits `PAGE_READY`; parent sets config (append-query/global data) then sends `PARENT_READY`. With `wait-for-parent-ready=true`, the page **blocks until `PARENT_READY`**.

**Query params** (currently from URL; if not iframe-embedded, take as `@Input`/route/query params): `admin-path`, `db-master-name`, `x-am-authorization`, `x-am-user-authorization`, `x-am-internationalization`, `wait-for-parent-ready`, `show-logs`.

**Iframe → Parent events** (`IDBMasterEventFromIframeToParent`): `PAGE_READY`, `RECORD_SAVED`, `RECORDS_IMPORTED`, `RECORD_UPDATED`, `RECORD_DELETED`, `MANY_RECORD_DELETED`, `GRID_EXPORTED`, `GRID_REFRESHED`, `ADD_NEW_BUTTON_CLICKED`, `CUSTOM_ACTION_BUTTON_CLICKED`, `DROPDOWN_ADD_NEW_RECORD_SAVED`. Plus custom events via `window.top.postMessage({ eventType, eventData }, '*')`.

**Parent → Iframe events** (`IDBMasterEventFromParentToIframe`): `TRIGGER_ADD_NEW_BUTTON_CLICK`, `TRIGGER_REFRESH_BUTTON_CLICK`, `DATA_TO_APPEND_IN_GRID_LOAD_FIND_QUERY` (merge into grid find query), `DATA_TO_APPEND_IN_RECORD_SAVE_API_PAYLOAD` (merge into create payload), `DATA_TO_APPEND_IN_RECORD_UPDATE_API_PAYLOAD` (merge into update payload), `SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT` (populate `globalData`), `PARENT_READY`.

The data layer **must** support merging an externally-supplied object into (a) the grid load/find query, (b) the save payload, and (c) the update payload. If replacing the iframe with normal Angular: expose `@Input() appendToFindQuery`, `@Input() appendToSavePayload`, `@Input() appendToUpdatePayload`, `@Input() globalData`, `@Output()` emitters for each iframe→parent event, and methods `triggerAddNew()` / `triggerRefresh()`. Preserve `wait-for-parent-ready` semantics (don't load until inputs are set) and `show-logs`. (For real iframe binding in Angular, sanitize the URL with `DomSanitizer.bypassSecurityTrustResourceUrl`.)

---

## 13. Date & timezone handling

- All dates use **date-fns** formats. Global `dateTimeFormat` default `'dd-MM-yyyy hh:mm:ss a'`; global `dateTimeZone` default browser tz. Dates are converted to `dateTimeZone` **before grid display AND before being sent into the DB query** (use date-fns-tz).
- Grid columns: `autoFormatDate:true` formats the cell (default `'dd-MM-yyyy hh:mm:ss a'`); per-column `dateTimeFormat`/`dateTimeZone` override.
- `date_picker` value is a JS `Date`. **PrimeNG calendar format caveat**: PrimeNG's `dateFormat` token syntax differs from date-fns — convert (e.g. `dd-MM-yyyy` → `dd-mm-yy`). **PrimeNG calendar has no time format token**, so time is **auto-appended** to the displayed value. Unsupported PrimeNG calendar tokens will break — validate during conversion.
- For `view='month'`/`'year'`, keep `dateTimeFormat` consistent (`'MM-yyyy'`/`'yyyy'`).
- Centralize all of this in `date-format.service.ts` (date-fns format string, date-fns → PrimeNG token conversion, tz conversion).

---

## 14. Step-by-step conversion procedure (per UI page)

For **each** UI Maker page:

1. **Read the page's `dbMasterConfig` in full** — it is authoritative. List every key actually used (don't port unused keys).
2. **Scaffold** the page component set under `pages/<db-master-name>/` (`.ts/.html/.scss`, `.main.ts`, `.api.ts`, `.model.ts`). Import the shared library.
3. **Build the `FormGroup`** from `form.fields[][]`: create nested `FormGroup`s for dotted `path`s; lay out rows/columns with `cssClassDiv` (compute `col-md-*` from columns count).
4. **Wire validations**: static `required` → `Validators.required`; control-specific (`minLength`, `maxLength`, `min`, `max`, `email`, `enum`, custom `validatorFun`) → matching validators; **`requiredFun`** → dynamic validator re-evaluated on `valueChanges` (takes precedence over static `required`). Surface `validationErrors.*` messages.
5. **Wire dropdowns/cascades**: implement `dataSource` (static/db/api), `optionLabel`/`optionValue` (HTML labels via templates), `filter`/`filterBy`/`filterMatchMode`, `virtualScroll`, `alwaysGetLatestDataOnFormOpen`, `addNewFormConfig`, and `reloadDropdownsOfPath` + `isDependentOnPath` + `modify*Request` cascades (§8).
6. **Wire file uploads** (§7.17): upload/download/remove contracts, placeholder substitution, response shape.
7. **Port `jsCode` hooks**: page-level (§4.2) → component methods/lifecycle; per-control → control event handlers. Resolve `userUtils.Main.*` (page) and `userUtils.GeneralUtils.*` (shared). Support string/function/reference, Promise, returned-function.
8. **Port `cssCode`** → page `<name>.scss`; common CSS → global `styles.scss`.
9. **Wire the API service**: standard endpoints + every `apiCallOverrides` (POST only, placeholder substitution, before/after hooks).
10. **Wire the grid** (§5): columns, sorting, **per-column row filter on every column** (§5.5.1), **global search** (§5.10.1), **column selector** (§5.10.2), pagination, grouping, selection, row actions + ordering + magic-hide flags, toolbar ops + ordering, custom action buttons + `confirmationMessageScript`.
11. **Wire CSV import** and **export (both CSV and PDF, all + selected)** (§10) and **theme** (§11).
12. **Wire parent integration** (§12): postMessage events / `@Input`/`@Output` bridge, append-query/save/update payload merging, `globalData`.
13. **Verify against the original** running page (§15) — behavior, not just structure.

---

## 15. Definition of Done / verification checklist

Tick **every** item for the page. This is the safety net for "do not miss any feature."

**Form controls** — every control present in the config is rendered correctly:
- [ ] input, textarea, password, inputNumber, inputMask, inputOtp, date_picker, checkbox, radio
- [ ] dropdown, auto_complete, multi_select (all three data sources: static_data / db_data / api_call)
- [ ] color_picker, editor (Quill formats whitelist), rating (custom icons), knob
- [ ] file_upload, image, button, divider, customHTML (sanitized), accordion, tab_view, nested grid

**Per-control fidelity:**
- [ ] Every settings property mapped (no property dropped)
- [ ] `optionLabel`/`optionValue` correct (auto_complete stores label-value, no optionValue)
- [ ] `filterMatchMode` set per control (autoComplete only contains/startsWith/endsWith)
- [ ] HTML in `optionLabel`/`helpText`/headers rendered via `[innerHTML]`
- [ ] inputMask model mutation done in `complete`, not `blur`
- [ ] inputNumber currency requires `currency`; locale default `en-US`; buttonLayout default `stacked`
- [ ] date_picker value is `Date`; PrimeNG format conversion; month/year view formats; time auto-appended

**Validation:**
- [ ] static `required`, dynamic `requiredFun` (precedence), `validationErrors` messages
- [ ] control-specific `minLength`/`maxLength`/`min`/`max`/`minFractionDigits`/`email`/`enum`/`validatorFun`
- [ ] `selectionLimit`, `forceSelection`, `maxFileSize`, `fileLimit`, `accept`

**Conditional logic:**
- [ ] `visible` / `disabled` as boolean OR JS-expression string, re-evaluated on value changes
- [ ] `requiredFun` re-evaluated dynamically
- [ ] tab/accordion `disabled` (accordion: bool|expr; tabView: bool only)

**Dependent / cascading dropdowns:**
- [ ] `reloadDropdownsOfPath`, `isDependentOnPath`, `modify*Request` working N levels, mixed control types
- [ ] children cleared + reloaded; parent value injected into child query; `once*DataLoaded` runs

**File upload:**
- [ ] upload (`files` field, returns `[{originalName, uploadPath}]`), download/preview (base64), remove (`{originalName, uploadPath}`)
- [ ] auto vs manual, multiple, accept, maxFileSize (10MB default), fileLimit, `:beHostPort`/`:userPath`

**Grid:**
- [ ] columns (auto-from-schema excludes `_id`), header/path/dotted paths, sorting, `visibilityStatus`, `autoFormatDate`/`dateTimeFormat`, `dbData` FK lookup, `cssClass`
- [ ] `sortMode`/`multiSortMeta`, pagination (showPagination/serverSide/rowsPerPage/options/report template), grouping (subheader), selection (mode/dataKey/metaKeySelection/checkboxSelection)
- [ ] **global search** (`showGlobalSearch` toolbar input; `globalSearchFields` incl. dotted paths; string→contains / number→exact; OR across fields; client `filterGlobal` + server `$or` query; debounced + clear) — see §5.10.1
- [ ] **column selector** (`showColumnSelector` toolbar `p-multiSelect`; per-column `visibilityStatus` seeds initial visible set; toggling shows/hides columns live; fires `columnSelectionChanged`) — see §5.10.2
- [ ] **row filter — a filter on EVERY data column** (`rowFilter` → `p-columnFilter` per column; type-aware inputs+match modes for string/number/date/boolean/FK; dotted paths; AND across columns; client built-in + server `find`-query translation via `modifyGridRequest`; action/selection columns excluded) — see §5.5.1
- [ ] `selectFieldsForQuery`, `minWidth`/`breakpoint`
- [ ] row ops (edit/view/delete) + cssClass + confirmations + double-click-edit + delete-button-click + `gridOperationsOrder`/`gridColumnsOrder`/`actionColumnWidth`
- [ ] per-row magic-hide flags (`____hide_delete_button`/`_edit_`/`_view_`/`____hide_<actionName>`)
- [ ] row custom action buttons + `confirmationMessageScript` (template-literal eval with `selectedGridItems`)
- [ ] toolbar ops + `operationsOrder` (default order), add/refresh/delete-all/export/import/column-selector/theme-selector + hide flags + `hideDeleteManySuccessMessage`; top-level custom action buttons

**Nested structures:**
- [ ] nested `fields[][]` layout containers; nested grid (`gridSettings`) as FormArray, unlimited depth; `dataFieldPath`; `addNewFormConfig` inline add flow

**API & overrides:**
- [ ] standard endpoints + all `apiCallOverrides` (add/save, edit, getById, delete, deleteMany, import, grid get-data, dropdown) — POST only
- [ ] `:beHostPort`/`:userPath` substitution; `codeBeforeAPICall`/`codeAfterAPICall` with correct scope vars; `pkField`

**CSV import / export (CSV + PDF) / theming / dates:**
- [ ] CSV import (Papa Parse, fieldMappings, type conversion, date parse/tz, UI display formats, preview grid widths)
- [ ] **CSV export** (export all + export selected; `csv.enable`/`labelCSV`/`labelCSVSelected`; `icon`; `processDataBeforeExport` mutable-copy hook; Papa `unparse`; visible columns + headers) — §10.2
- [ ] **PDF export** (export all + export selected; `pdf.enable`/`labelPDF`/`labelPDFSelected`; `processDataBeforePDFExport` hook with replaceable `jsPdfDoc`; `window.jspdf` loaded; tabular render; visible columns + headers) — §10.2
- [ ] `GRID_EXPORTED` parent event emitted on export (both formats)
- [ ] theme applied + `showThemeSelector`; global `dateTimeFormat`/`dateTimeZone` defaults; date-fns ↔ PrimeNG conversion

**JS / CSS hooks:**
- [ ] page-level `jsCode` hooks (all `EDBMasterConfigAppendTo` used) with correct scope (`config`, `globalData`, `utils`, `queryParams`, `gridEvent`, `gridData`, `reqBody`, `formData`, `mode`)
- [ ] per-control `jsCode` events wired to PrimeNG outputs; string/function/`userUtils.*` refs; Promise/returned-function support
- [ ] `utils` helpers (`findFormElement`, `getFlatArrayOfAllFormFields[IncludingGridFields]`, `messageService.*`, `errorsMap`)
- [ ] common CSS → global styles; page CSS → page styles; common JS → `GeneralUtils`; page JS → `Main`
- [ ] external libs (`externalLibs.css`/`js` CDNs loaded before user code); inbuilt libs available: `Papa`, `dateFns`, `dateFnsTz`, `_`, `moment`; Bootstrap 5.3.3 grid+utilities

**Parent integration:**
- [ ] all iframe→parent and parent→iframe events; handshake (`PAGE_READY`/`PARENT_READY`); `wait-for-parent-ready`; `show-logs`
- [ ] append-data merging into find query / save payload / update payload; `SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT` → `globalData`; query params

When any item cannot be satisfied from the config alone, **inspect the original UI Maker page at runtime** and reproduce the observed behavior exactly. Do not skip, do not approximate.
