Automates the production manager's manual workflow of checking xTuple WO Schedule + Kit Material Shortage for FA department work orders. Two tabs: WO Shortages (detail per WO + shortage line with customer name, YYYY-MM-DD dates) and Critical Parts (aggregated parts blocking near-term shipments with QOH from MPE warehouse). Nav button added to all pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
217 lines
6.7 KiB
Markdown
217 lines
6.7 KiB
Markdown
---
|
|
name: add-tabs-to-page
|
|
description: Adds a TABS_WIDGET to a page so multiple data views live under one page instead of separate pages. Use when the user asks to add tabs, consolidate pages into tabs, create tabbed views, or replace a single table with a tabbed layout.
|
|
---
|
|
|
|
# Add Tabs to a Page
|
|
|
|
|
|
Replaces a page's standalone content (e.g. a single `TABLE_WIDGET_V2`) with a `TABS_WIDGET` containing multiple tabs, each with its own widgets and query binding.
|
|
|
|
## Prerequisites
|
|
|
|
- **Tab definitions**: User must provide the tab labels and the query each tab should display.
|
|
- **Page prefix**: Each page uses a short widget-ID prefix (e.g. `pa1`, `cp1`). Reuse the existing prefix from the page's widgets.
|
|
- Follow the **add-query-to-table** skill for creating any new queries needed by the tabs.
|
|
|
|
## File Layout
|
|
|
|
The Tabs widget and all widgets **inside** tabs live under a `Tabs1/` directory:
|
|
|
|
```
|
|
pages/<PageName>/widgets/Tabs1/
|
|
├── Tabs1.json # The TABS_WIDGET itself
|
|
├── <Table1>.json # Table inside tab 1
|
|
├── <Table2>.json # Table inside tab 2
|
|
├── <DatePicker>.json # Optional: date pickers inside a tab
|
|
└── ...
|
|
```
|
|
|
|
If the page previously had a standalone `Table1.json` at `widgets/Table1.json`, **delete it** after creating the tabbed replacement.
|
|
|
|
## Instructions
|
|
|
|
### 1. Design the tab structure
|
|
|
|
Decide on tab count, labels, and content. Example:
|
|
|
|
| Tab ID | Label | Canvas widgetId | Content |
|
|
|--------|-------|-----------------|---------|
|
|
| tab1 | All | `{prefix}cnvsall` | TableAll bound to `query_all` |
|
|
| tab2 | SLx | `{prefix}cnvsslx` | TableSLx bound to `query_slx` |
|
|
|
|
### 2. Create `Tabs1.json`
|
|
|
|
The TABS_WIDGET has three critical sections:
|
|
|
|
**A) `tabsObj`** — declares each tab with id, label, index, and canvas widgetId:
|
|
|
|
```json
|
|
"tabsObj": {
|
|
"tab1": {
|
|
"id": "tab1",
|
|
"index": 0,
|
|
"isVisible": true,
|
|
"label": "All",
|
|
"positioning": "vertical",
|
|
"widgetId": "<canvas-widgetId-for-tab1>"
|
|
},
|
|
"tab2": { ... }
|
|
}
|
|
```
|
|
|
|
**B) `children`** — array of `CANVAS_WIDGET` entries, one per tab. Each canvas must reference:
|
|
- `"parentId"`: the Tabs widget's `widgetId`
|
|
- `"tabId"`: matching key from `tabsObj` (e.g. `"tab1"`)
|
|
- `"tabName"`: the display label
|
|
- `"widgetId"`: unique canvas ID (referenced in `tabsObj` and by child widgets)
|
|
|
|
**C) Top-level Tabs properties:
|
|
|
|
```json
|
|
{
|
|
"type": "TABS_WIDGET",
|
|
"version": 3,
|
|
"isCanvas": true,
|
|
"shouldShowTabs": true,
|
|
"defaultTab": "<label of default tab>",
|
|
"parentId": "0",
|
|
"leftColumn": 9,
|
|
"rightColumn": 64,
|
|
"topRow": 5,
|
|
"bottomRow": 67,
|
|
"dynamicHeight": "AUTO_HEIGHT",
|
|
"dynamicBindingPathList": [
|
|
{"key": "accentColor"},
|
|
{"key": "boxShadow"}
|
|
],
|
|
"accentColor": "{{appsmith.theme.colors.primaryColor}}",
|
|
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}",
|
|
"backgroundColor": "#FFFFFF",
|
|
"borderColor": "#E0DEDE",
|
|
"borderRadius": "0.375rem",
|
|
"borderWidth": 1
|
|
}
|
|
```
|
|
|
|
### 3. Create widgets inside each tab
|
|
|
|
Each widget inside a tab sets `"parentId"` to that tab's **canvas widgetId** (not the Tabs widgetId). Widgets start at `topRow: 0` within each canvas.
|
|
|
|
**Table-only tabs** (no date pickers):
|
|
|
|
```json
|
|
{
|
|
"type": "TABLE_WIDGET_V2",
|
|
"parentId": "<canvas-widgetId>",
|
|
"topRow": 0,
|
|
"bottomRow": 58,
|
|
"leftColumn": 0,
|
|
"rightColumn": 62,
|
|
"tableData": "{{<query_name>.data}}",
|
|
...
|
|
}
|
|
```
|
|
|
|
**Tabs with date pickers** (see Sales - Units Shipped for reference):
|
|
|
|
| Widget | topRow | bottomRow | leftColumn | rightColumn |
|
|
|--------|--------|-----------|------------|-------------|
|
|
| DateFrom | 0 | 7 | 0 | 5 |
|
|
| DateTo | 0 | 7 | 5 | 10 |
|
|
| Table | 7 | 58 | 0 | 62 |
|
|
|
|
Date pickers use `"type": "DATE_PICKER_WIDGET2"`, `"dateFormat": "YYYY-MM-DD HH:mm"`, and `"parentId"` set to the tab's canvas widgetId.
|
|
|
|
### 4. Create queries for each tab
|
|
|
|
Follow the **add-query-to-table** skill. Each query's `metadata.json` must have:
|
|
- `"pageId"`: the page name (e.g. `"Pending POs"`)
|
|
- `"gitSyncId"`: use the same 24-char hex prefix as the page, with a unique UUID suffix
|
|
- `"runBehaviour": "AUTOMATIC"`
|
|
|
|
### 5. Delete the old standalone widget
|
|
|
|
If the page had a `widgets/Table1.json` (or similar) that the Tabs widget replaces, delete it.
|
|
|
|
### 6. Verify
|
|
|
|
- Each canvas `widgetId` in `children` matches the corresponding `widgetId` in `tabsObj`.
|
|
- Each widget inside a tab has `parentId` set to its tab's canvas `widgetId`.
|
|
- `defaultTab` matches one of the tab labels (not the tab ID).
|
|
- All queries reference the correct `pageId`.
|
|
- Run `git status` / `git diff` to confirm only intended changes.
|
|
|
|
## Canvas Widget Template
|
|
|
|
Each canvas child in the `children` array follows this template:
|
|
|
|
```json
|
|
{
|
|
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
|
|
"bottomRow": 620,
|
|
"boxShadow": "{{appsmith.theme.boxShadow.appBoxShadow}}",
|
|
"canExtend": true,
|
|
"detachFromLayout": true,
|
|
"dynamicBindingPathList": [
|
|
{"key": "borderRadius"},
|
|
{"key": "boxShadow"}
|
|
],
|
|
"dynamicHeight": "AUTO_HEIGHT",
|
|
"dynamicTriggerPathList": [],
|
|
"flexLayers": [],
|
|
"isDisabled": false,
|
|
"isLoading": false,
|
|
"isVisible": true,
|
|
"key": "<unique-key>",
|
|
"leftColumn": 0,
|
|
"maxDynamicHeight": 9000,
|
|
"minDynamicHeight": 4,
|
|
"minHeight": 150,
|
|
"minWidth": 450,
|
|
"mobileBottomRow": 150,
|
|
"mobileLeftColumn": 0,
|
|
"mobileRightColumn": 602.625,
|
|
"mobileTopRow": 0,
|
|
"needsErrorInfo": false,
|
|
"parentColumnSpace": 1,
|
|
"parentId": "<tabs-widgetId>",
|
|
"parentRowSpace": 1,
|
|
"renderMode": "CANVAS",
|
|
"responsiveBehavior": "fill",
|
|
"rightColumn": 602.625,
|
|
"shouldScrollContents": false,
|
|
"tabId": "<tab-id>",
|
|
"tabName": "<tab-label>",
|
|
"topRow": 0,
|
|
"type": "CANVAS_WIDGET",
|
|
"version": 1,
|
|
"widgetId": "<unique-canvas-widgetId>",
|
|
"widgetName": "Canvas<N>"
|
|
}
|
|
```
|
|
|
|
## Consolidating Multiple Pages into Tabs
|
|
|
|
When merging separate pages into one tabbed page:
|
|
|
|
1. **Choose the surviving page** (or create a new one via **create-new-page** skill).
|
|
2. **Move each page's query** into the surviving page's `queries/` directory. Update `pageId` and generate new `gitSyncId` values (same 24-char prefix as the surviving page).
|
|
3. **Create the Tabs widget** with one tab per former page, plus any additional tabs (e.g. "All").
|
|
4. **Delete the old page directories** entirely.
|
|
5. **Remove old navigation buttons** from all remaining pages (delete the button JSON files).
|
|
6. **Update `application.json`** to remove entries for deleted pages.
|
|
7. **Keep one nav button** pointing to the surviving page.
|
|
|
|
## Reference
|
|
|
|
| Item | Value |
|
|
|------|-------|
|
|
| Widget type | `TABS_WIDGET` version `3` |
|
|
| Widget directory | `widgets/Tabs1/` |
|
|
| Canvas parent | Tabs `widgetId` |
|
|
| Widget-in-tab parent | Canvas `widgetId` for that tab |
|
|
| `defaultTab` | Tab **label** (not tab ID) |
|
|
| `tabsObj` keys | `tab1`, `tab2`, `tab3`, ... |
|
|
| Existing examples | `Sales - Units Shipped`, `Pending POs` |
|