# Form Builder

The Form Builder lets administrators create custom data entry forms without editing code.
Definitions are stored in four metadata tables:


- `forms` – top level form records with the form name and optional description.
- `form_buttons` – action buttons shown below the fields.
- `form_elements` – fields for each form including label, field name, type and
  validation options.
*Deprecated*: table views have been removed. Listing logic now belongs in each
model and is rendered via `DataLoader`.

Default forms shipped with TAF are persisted in the `forms` table with their
fields serialised to the `elements` JSON column. The `form_elements` table is
still used when editing forms in the builder.

When adding forms via SQL migrations or seed files you should insert the entire
layout into the `forms.elements` column as JSON. Examples can be found in
`sql/defaults.sql` where the **Risk Identification** form (ID 80) and
**Workplace Registration** form (ID 98) define their fields inside a JSON array
and previously included a default table view. Table views are no longer used, so
avoid inserting rows into `form_elements` for seed data.

Forms may also store a flat array of field definitions without sections. The
following JSON would be saved directly to `forms.elements`:

```json
[
  {"label": "Name", "field_name": "name", "type_id": 1, "order_index": 0},
  {"label": "Age", "field_name": "age", "type_id": 2, "order_index": 1}
]
```

Each record includes `tenant_id` and `branch_id` so forms can be scoped to a specific
organisation. Controllers load definitions through `FormBuilderController` and cache
results via `CacheService`.
`FormConfigService` resolves the correct form at runtime using the department,
tenant and super admin hierarchy described in
[`HierarchicalConfiguration.md`](HierarchicalConfiguration.md).

## Creating a Form

1. Open the **Form Builder** module from the dashboard.
2. Add a new form and populate the title and description.
3. Define fields by dragging items from the toolbox into the canvas. See the
   supported field types below for details on each input.
4. Save the form to persist the metadata records. Any cached copy is invalidated
   automatically.

The form name, description and submit workflow are configured in the **Form Settings** accordion on the right side of the builder. The builder now mirrors the three column layout used by the **Module Builder** so administrators have a consistent drag and drop experience across tools.

### Public Sharing

Each form can now expose a public link. Choose the **Share Scope** inside **Form Settings**:

- `Private` keeps the form internal (default).
- `This organisation` publishes a tenant-specific link. The slug is scoped to the current domain.
- `All organisations` publishes a global link derived from the base form definition.

When sharing is enabled the builder displays the shareable URL and provides a *Regenerate Link* button. Saving with the scope set back to *Private* disables public access.

### UI Layout

The interface is split into three columns just like the Module Builder:

- **Palette** – vertical list of available field types. Items can be dragged from here onto the canvas.
- **Canvas** – central workspace where field cards are arranged.
- **Inspector** – right hand panel with *Field Properties* and *Form Settings* accordions.

```
+-----------+--------------------+-------------+
| Palette   |      Canvas        |  Inspector  |
| drag a    | arrange fields in  | edit the    |
| field     | rows and columns   | selected    |
| here      |                    | field/form  |
+-----------+--------------------+-------------+
```

Every field is rendered as a DaisyUI `card`. The header shows a drag handle, the current label and an icon representing the type. Clicking the header toggles the card body which contains inline inputs for the label, placeholder, type, required flag, regex pattern and data column. Options for dropdown fields appear in a table at the bottom of the body.
When a field type is added its label defaults to the type name and the internal `field_name` uses a snake_case version. Updating the label automatically adjusts `field_name`.

Fields can be reordered via drag and drop by grabbing the header handle.

### Supported Field Types

The toolbox offers a variety of inputs. Most common HTML types such as
`text`, `number`, `date`, `select` and `checkbox` work as you would
expect. TAF also includes specialised fields:

- **gps** – renders a hidden input with a *Get Location* button. When
  pressed the browser's geolocation API stores the coordinates as
  `latitude,longitude`.
- **camera** – identical to a file input but adds the `capture` attribute
  so mobile browsers open the camera. Uploaded images or videos are sent
  through the same file upload flow used by regular `file` fields.

DataLoader and the Kotlin mobile apps handle camera uploads using the
same endpoints and database columns as other file attachments. GPS values
are persisted as the comma separated string returned by the browser.

### Dynamic Select Fields

Select elements can pull their options from any table instead of using a hard
coded list. Three new columns were added to `form_elements` for this purpose:

- `options_table` – table name that stores the records.
- `options_label_column` – column used for the option text.
- `options_value_column` – column used for the option value.

**Configuring**

1. Add or select a field with type `select`.
2. In the inspector panel expand **Field Properties**.
3. Choose the source table from the **Options Table** dropdown. The list is
   built from your local schema so you don't need to type the name manually.
4. Start typing in the *Label Column* or *Value Column* fields to see
   suggestions from the selected table.
5. Click **Refresh Options** to preview the values returned from the API.

When a form loads, any select field with these settings automatically issues a
`GET /api/form_builder/dynamic_options/{elementId}` request. The endpoint
responds with JSON in the format:

```json
[
  { "value": 1, "label": "Option name" }
]
```

The refresh button performs the same call so administrators can verify the
configuration while building the form.

Fields using the `select-addable` type follow the same configuration but also
display an input next to the dropdown so users can append new values. The entry
is inserted into the specified `options_table` through the sync manager and the
select refreshes immediately.


The `multiselect-search` type provides a searchable multi-select widget. Set the
`options_table`, `options_label_column` and `options_value_column` the same way
as regular select fields. Values are loaded through the sync manager and the
component stores the selected IDs as a comma separated list.

### Filtering Dynamic Options by Column
Select elements can restrict the records fetched from the source table by
setting a `filter_columns` object on the field validation. Each entry maps a
column name to an allowed value or array of values. Dot notation can be used for
nested properties.

```json
{
  "label": "assigned_representative",
  "field_name": "lead_rep_id",
  "type_id": 4,
  "options_table": "users",
  "options_label_column": "full_name",
  "options_value_column": "id",
  "validation": {
    "filter_columns": {
      "groups.name": ["lawyer", "lawyers"]
    }
  }
}
```

Older forms may still use `filter_groups`. When present it will be applied as a
fallback, but new forms should prefer `filter_columns` for greater flexibility.

### Conditional Fields

Use `condition_field` and `condition_value` to show a field only when another
input has a matching value. Set these properties under **Field Properties** in
the inspector.

Example: a checkbox named `show_more` reveals a dropdown when checked.

```json
[
  { "label": "Show more options", "field_name": "show_more", "type_id": 3 },
  {
    "label": "Extra Option",
    "field_name": "extra",
    "type_id": 4,
    "condition_field": "show_more",
    "condition_value": "1",
    "options": [ { "value": "a", "label": "Option A" } ]
  }
]
```

All forms rendered through `FormRenderer` automatically evaluate these conditions so the dependent fields hide and reveal as users interact with the controlling input.

To match multiple values, separate them with a pipe (`|`). For instance, `"condition_value": "a|b"` displays the field when the controlling input has a value of either `a` or `b`.

### Checkbox Table
The `checkbox-table` type renders a grid of checkboxes defined by **fields**
(table columns) and **rows**. Each cell stores a boolean value. Use this when a
form needs to capture multiple related options such as an access matrix.

Example configuration:

```json
{
  "name": "access",
  "type": "checkbox-table",
  "fields": [
    { "name": "read",  "label": "Read" },
    { "name": "write", "label": "Write" }
  ],
  "rows": [
    { "name": "files",   "label": "Files" },
    { "name": "reports", "label": "Reports" }
  ]
}
```

Rendered table:

```
|         | Read | Write |
|---------|------|-------|
| Files   | [ ]  | [ ]   |
| Reports | [ ]  | [ ]   |
```

### Input Table
The `input-table` type captures a grid of numeric inputs similar to a small
spreadsheet. The JSON configuration defines **columns** and **rows**. Each
column object requires a `name` and `label` with an optional `formula`
expression. Formulas reference other columns in the same row and are evaluated
on the client so calculated values update as users type.

```json
{
  "name": "work_hours",
  "type": "input-table",
  "columns": [
    { "name": "hours", "label": "Hours" },
    { "name": "rate",  "label": "Rate" },
    { "name": "total", "label": "Total", "formula": "hours * rate" }
  ],
  "rows": [
    { "name": "task_a", "label": "Task A" },
    { "name": "task_b", "label": "Task B" }
  ]
}
```

Rendered table:

```
|        | Hours | Rate | Total |
|--------|-------|------|-------|
| Task A | [ ]   | [ ]  | =hours*rate |
| Task B | [ ]   | [ ]  | =hours*rate |
```

When the Form Builder mounts it checks the `element_types` store and triggers a
sync when the metadata count differs from the number of local records. Passing a
`forceRefresh` flag will also refresh the store so newly added field types appear
in the palette.

### Form Buttons
Buttons can trigger actions like submitting the form or starting a workflow. Each record stores a label, action type and payload. Supported actions are `submit`, `workflow`, `api` and `open-form`. Buttons are ordered using `order_index` and render after the form fields.
### Using Forms in Dashboard Modules

Dashboard modules and UI action buttons should open forms through
`FormSubmission.openForm(formId)`.
This helper loads field definitions and dynamic options then injects the form
into `#module-modal` so components such as **select-addable** and
**multiselect-search** work consistently.
It relies on `getCachedForm()` from `FrontEnd/JsLibs2/core/FormCache.js` which
retrieves form metadata from the local IndexedDB store. The cache is
automatically refreshed when a form is not found and an error toast is
displayed if the form cannot be retrieved.

## API Endpoints

The controller exposes REST style endpoints:

- `GET /api/forms` – list forms
- `GET /api/forms/{id}` – fetch a single form with fields
- `POST /api/forms` – create a form and form elements
- `PUT /api/forms/{id}` – update a form and replace its fields
- `DELETE /api/forms/{id}` – remove a form and related metadata

All endpoints require authentication and will scope results to the caller's
`tenant_id` and `branch_id`.

### Overriding Global Forms

Default forms are loaded from `forms.elements` which stores each field as JSON.
When administrators customise a form the builder writes individual rows to the
`form_elements` table and updates the JSON column. The system still reads the
legacy `form_definitions` table only for backward compatibility. To override a
global form insert a record into `form_definitions` with `base_form_id` set to
the form ID and scope it using `tenant_id` and optionally `branch_id`.
`FormConfigService` resolves overrides in department, tenant then global order.
### Example

Form ID 6 defines the **Job Safety Analysis** form. It is seeded in `sql/defaults.sql` and maps to the `ohs_jsas` table with fields `title`, `description` and `doc_path`.

## Form Submission Storage

Form submissions are captured in two tables:

- `form_submissions` stores a single row per form instance with the `form_id`, user and tenant context.
- `form_submission_values` stores one value per form element. Each row links back to the submission via `submission_id` and references the element by `element_id`.

This design follows an entity–attribute–value (EAV) pattern so dynamic forms can hold any number of fields. Values are typed according to their element definition and persisted in the `value` column. Numeric and date inputs are cast when reading or filtering.

### Offline Submission Flow

`openForm()` and `openWizard()` store submissions through `LocalSyncManager`. The
manager for the `form_submissions` table uses `../api/sync` as its
`bulkEndpoint`, so new entries are saved to IndexedDB when the browser is
offline and pushed automatically once connectivity is restored.

### Query Considerations

- Indexes exist on `form_submissions.form_id` and `form_submission_values.element_id` for efficient lookups.
- Always include `tenant_id` and `branch_id` in queries to scope results to the current organisation.
- When filtering numeric or date fields cast the `value` column to the appropriate type so indexes can be used effectively.

### Mapping to Core Tables

Forms can write directly into an existing table by setting the `mapped_table` column on the form record. Each field that should persist to this table must define `data_column` with the target column name. When the target column stores JSON data you can also specify `json_column` with the key name inside that JSON object. When a submission is received `FormSubmissionsController` checks for this mapping and will insert or update the row in the specified table instead of using `form_submissions` and `form_submission_values`.

When multiple fields share the same `data_column` but specify different `json_column` values the front-end groups them into a single object. `FormSubmission.js` builds this object under that column name before sending the record to the API. When editing a record the JSON is unpacked so each field shows the value from its corresponding key.

#### Column Suggestions

Choosing a table from the **Mapped Table** dropdown automatically retrieves its
columns from the backend. The builder issues a `GET /api/tables/{table}/columns`
request and stores the returned array. Each form field shows these names in a
`datalist` tied to the *Data Column* input so administrators can select a column
from the drop‑down or type one manually. The list refreshes whenever a new
table is selected.
The builder also provides a *JSON Column* input for specifying a key within a JSON column when forms write into structured data.

### Local Table List

`LocalSyncManager.getTables()` accepts a `localOnly` parameter. The Form Builder
now invokes `getTables()` without this flag so table names are first requested
from the backend (`/api/tables`). The endpoint merges table names returned from
`information_schema.tables` with active records in the `tenant_tables` table and
responds with the unique sorted list. When offline or the request fails, the
manager falls back to IndexedDB to populate the mapped table dropdown.
If no stores exist the select remains empty.

### Styling Conventions

Form Builder screens rely on DaisyUI components. Major sections such as the toolbox, canvas and inspector are wrapped in `card` elements with `p-4` padding to keep spacing consistent. Section headers like **Form Settings** and field card titles use the `text-lg` and `font-bold` classes for emphasis. The inspector uses the `collapse` component for accordions and `divide-y` utilities provide subtle lines between stacked areas. Background utilities such as `bg-base-100` help visually separate cards from the page backdrop.


## Responsive Layout

Below `md` screens, the field toolbox collapses to icons only and the inspector slides over the canvas so users can edit fields on small devices. Drag the gutter between columns to resize when multiple columns are enabled.

The diagram above shows the palette, canvas and inspector sections as they appear in the latest layout. They function exactly like the same areas in the Module Builder.
