Add Operations - WO Shortages page with MTS/MTO shortage tracking
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>
This commit is contained in:
216
.cursor/skills/add-tabs-to-page/SKILL.md
Normal file
216
.cursor/skills/add-tabs-to-page/SKILL.md
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
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` |
|
||||
Reference in New Issue
Block a user