From b5e74cebb7ca721ab99507a548700dad255c7970 Mon Sep 17 00:00:00 2001 From: Adam Pitel Date: Fri, 3 Apr 2026 13:47:58 -0400 Subject: [PATCH] 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) --- .cursor/rules/no-docs-directory.mdc | 12 + .cursor/skills/.DS_Store | Bin 0 -> 8196 bytes .cursor/skills/add-query-to-table/SKILL.md | 107 ++++ .cursor/skills/add-tabs-to-page/SKILL.md | 216 ++++++++ .cursor/skills/create-new-page/SKILL.md | 119 +++++ .cursor/skills/navigation-button-add/SKILL.md | 54 ++ .../skills/navigation-section-add/SKILL.md | 124 +++++ .cursor/skills/prepare-for-launch/SKILL.md | 53 ++ .gitignore | 9 + AGENTS.md | 159 ++++++ README.md | 88 +++- application.json | 4 + docs/README.md | 4 + .../widgets/Container1/Button1.json | 6 +- .../widgets/Container1/Button1Copy.json | 6 +- .../widgets/Container1/Button1Copy2.json | 6 +- .../widgets/Container1/Button2All.json | 6 +- .../widgets/Container1/Button2Copy.json | 6 +- .../widgets/Container1/Button2Copy2.json | 6 +- .../widgets/Container1/Button3.json | 6 +- .../widgets/Container1/Button3Copy.json | 10 +- .../widgets/Container1/Button3Copy2.json | 6 +- .../widgets/Container1/Button3Copy3.json | 45 ++ .../widgets/Container1/Container1.json | 12 +- .../widgets/Container1/Text1.json | 14 +- .../widgets/Container1/Text2.json | 14 +- .../widgets/Container1/Text3.json | 14 +- .../widgets/Heading.json | 12 +- .../widgets/Container1/Button1.json | 6 +- .../widgets/Container1/Button1Copy.json | 6 +- .../widgets/Container1/Button1Copy2.json | 6 +- .../widgets/Container1/Button2All.json | 6 +- .../widgets/Container1/Button2Copy.json | 6 +- .../widgets/Container1/Button2Copy2.json | 6 +- .../widgets/Container1/Button3.json | 10 +- .../widgets/Container1/Button3Copy.json | 6 +- .../widgets/Container1/Button3Copy2.json | 6 +- .../widgets/Container1/Button3Copy3.json | 45 ++ .../widgets/Container1/Container1.json | 12 +- .../widgets/Container1/Text1.json | 14 +- .../widgets/Container1/Text2.json | 14 +- .../widgets/Container1/Text3.json | 14 +- .../widgets/Heading.json | 12 +- .../Operations - Unused Items.json | 2 +- .../widgets/Container1/Button1.json | 4 +- .../widgets/Container1/Button1Copy.json | 4 +- .../widgets/Container1/Button1Copy2.json | 4 +- .../widgets/Container1/Button2All.json | 4 +- .../widgets/Container1/Button2Copy.json | 4 +- .../widgets/Container1/Button2Copy2.json | 4 +- .../widgets/Container1/Button3.json | 4 +- .../widgets/Container1/Button3Copy.json | 4 +- .../widgets/Container1/Button3Copy2.json | 2 +- .../widgets/Container1/Button3Copy3.json | 45 ++ .../widgets/Container1/Container1.json | 12 +- .../widgets/Container1/Text1.json | 12 +- .../widgets/Container1/Text2.json | 12 +- .../widgets/Container1/Text3.json | 12 +- .../widgets/Heading.json | 14 +- .../Operations - WO Shortages.json | 33 ++ .../queries/critical_parts/critical_parts.txt | 65 +++ .../queries/critical_parts/metadata.json | 31 ++ .../queries/wo_shortages/metadata.json | 31 ++ .../queries/wo_shortages/wo_shortages.txt | 112 +++++ .../widgets/Container1/Button1.json | 45 ++ .../widgets/Container1/Button1Copy.json | 45 ++ .../widgets/Container1/Button1Copy2.json | 45 ++ .../widgets/Container1/Button2All.json | 45 ++ .../widgets/Container1/Button2Copy.json | 45 ++ .../widgets/Container1/Button2Copy2.json | 45 ++ .../widgets/Container1/Button3.json | 45 ++ .../widgets/Container1/Button3Copy.json | 45 ++ .../widgets/Container1/Button3Copy2.json | 45 ++ .../widgets/Container1/Button3Copy3.json | 49 ++ .../widgets/Container1/Container1.json | 87 ++++ .../widgets/Container1/Text1.json | 46 ++ .../widgets/Container1/Text2.json | 46 ++ .../widgets/Container1/Text3.json | 46 ++ .../widgets/Heading.json | 52 ++ .../widgets/Tabs1/Tabs1.json | 150 ++++++ .../widgets/Tabs1/ws1tblparts.json | 251 ++++++++++ .../widgets/Tabs1/ws1tblshort.json | 474 ++++++++++++++++++ pages/Pending POs/Pending POs.json | 2 +- .../queries/pending_pos_all/metadata.json | 2 +- .../pending_pos_alx_pending/metadata.json | 2 +- .../pending_pos_sr_pending/metadata.json | 2 +- .../widgets/Container1/Button1.json | 2 +- .../widgets/Container1/Button1Copy.json | 2 +- .../widgets/Container1/Button1Copy2.json | 2 +- .../widgets/Container1/Button2All.json | 2 +- .../widgets/Container1/Button2Copy.json | 2 +- .../widgets/Container1/Button2Copy2.json | 4 +- .../widgets/Container1/Button3.json | 6 +- .../widgets/Container1/Button3Copy.json | 6 +- .../widgets/Container1/Button3Copy2.json | 6 +- .../widgets/Container1/Button3Copy3.json | 45 ++ .../widgets/Container1/Container1.json | 2 +- .../Pending POs/widgets/Container1/Text1.json | 2 +- .../Pending POs/widgets/Container1/Text2.json | 2 +- .../Pending POs/widgets/Container1/Text3.json | 14 +- pages/Pending POs/widgets/Heading.json | 2 +- pages/Pending POs/widgets/Tabs1/TableALx.json | 324 ++++++------ pages/Pending POs/widgets/Tabs1/TableML.json | 324 ++++++------ pages/Pending POs/widgets/Tabs1/TableSLx.json | 324 ++++++------ pages/Pending POs/widgets/Tabs1/TableSR.json | 324 ++++++------ .../Pending Revisions/Pending Revisions.json | 2 +- .../pending_revisions_all/metadata.json | 2 +- .../pending_revisions_alx/metadata.json | 2 +- .../pending_revisions_ml/metadata.json | 2 +- .../pending_revisions_slx/metadata.json | 2 +- .../pending_revisions_sr/metadata.json | 2 +- .../widgets/Container1/Button1.json | 2 +- .../widgets/Container1/Button1Copy.json | 2 +- .../widgets/Container1/Button1Copy2.json | 2 +- .../widgets/Container1/Button2All.json | 2 +- .../widgets/Container1/Button2Copy.json | 2 +- .../widgets/Container1/Button2Copy2.json | 4 +- .../widgets/Container1/Button3.json | 6 +- .../widgets/Container1/Button3Copy.json | 6 +- .../widgets/Container1/Button3Copy2.json | 6 +- .../widgets/Container1/Button3Copy3.json | 45 ++ .../widgets/Container1/Container1.json | 2 +- .../widgets/Container1/Text1.json | 2 +- .../widgets/Container1/Text2.json | 2 +- .../widgets/Container1/Text3.json | 14 +- pages/Pending Revisions/widgets/Heading.json | 2 +- .../widgets/Tabs1/TableALx.json | 24 +- .../widgets/Tabs1/TableML.json | 24 +- .../widgets/Tabs1/TableSLx.json | 24 +- .../widgets/Tabs1/TableSR.json | 24 +- .../widgets/Container1/Button2All.json | 2 +- .../widgets/Container1/Button2Copy.json | 2 +- .../widgets/Container1/Button2Copy2.json | 4 +- .../widgets/Container1/Button3.json | 6 +- .../widgets/Container1/Button3Copy.json | 6 +- .../widgets/Container1/Button3Copy2.json | 6 +- .../widgets/Container1/Button3Copy3.json | 45 ++ .../widgets/Container1/Text2.json | 2 +- .../widgets/Container1/Text3.json | 14 +- .../widgets/Container1/Button2All.json | 2 +- .../widgets/Container1/Button2Copy.json | 2 +- .../widgets/Container1/Button2Copy2.json | 4 +- .../widgets/Container1/Button3.json | 4 +- .../widgets/Container1/Button3Copy.json | 4 +- .../widgets/Container1/Button3Copy2.json | 6 +- .../widgets/Container1/Button3Copy3.json | 45 ++ .../widgets/Container1/Text2.json | 2 +- .../widgets/Container1/Text3.json | 14 +- .../widgets/Container1/Button2All.json | 2 +- .../widgets/Container1/Button2Copy.json | 2 +- .../widgets/Container1/Button2Copy2.json | 4 +- .../widgets/Container1/Button3.json | 4 +- .../widgets/Container1/Button3Copy.json | 4 +- .../widgets/Container1/Button3Copy2.json | 6 +- .../widgets/Container1/Button3Copy3.json | 45 ++ .../widgets/Container1/Text2.json | 2 +- .../widgets/Container1/Text3.json | 14 +- pages/xGen/widgets/Container1/Button1.json | 4 +- .../xGen/widgets/Container1/Button1Copy.json | 4 +- .../xGen/widgets/Container1/Button1Copy2.json | 4 +- pages/xGen/widgets/Container1/Button2All.json | 4 +- .../xGen/widgets/Container1/Button2Copy.json | 4 +- .../xGen/widgets/Container1/Button2Copy2.json | 8 +- pages/xGen/widgets/Container1/Button3.json | 6 +- .../xGen/widgets/Container1/Button3Copy.json | 6 +- .../xGen/widgets/Container1/Button3Copy2.json | 6 +- .../xGen/widgets/Container1/Button3Copy3.json | 45 ++ pages/xGen/widgets/Container1/Container1.json | 12 +- pages/xGen/widgets/Container1/Text1.json | 12 +- pages/xGen/widgets/Container1/Text2.json | 12 +- pages/xGen/widgets/Container1/Text3.json | 14 +- pages/xGen/widgets/Heading.json | 12 +- pages/xGen/widgets/Tabs1/TableCompat.json | 20 +- pages/xGen/widgets/Tabs1/TableForward.json | 20 +- pages/xGen/widgets/Tabs1/TableHwSw.json | 20 +- pages/xGen/widgets/Tabs1/TablePairs.json | 20 +- pages/xGen/widgets/Tabs1/TableSnapshot.json | 20 +- pages/xGen/widgets/Tabs1/TableTestCal.json | 20 +- 178 files changed, 4158 insertions(+), 1286 deletions(-) create mode 100644 .cursor/rules/no-docs-directory.mdc create mode 100644 .cursor/skills/.DS_Store create mode 100644 .cursor/skills/add-query-to-table/SKILL.md create mode 100644 .cursor/skills/add-tabs-to-page/SKILL.md create mode 100644 .cursor/skills/create-new-page/SKILL.md create mode 100644 .cursor/skills/navigation-button-add/SKILL.md create mode 100644 .cursor/skills/navigation-section-add/SKILL.md create mode 100644 .cursor/skills/prepare-for-launch/SKILL.md create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 docs/README.md create mode 100644 pages/Operations - Engineering Holds/widgets/Container1/Button3Copy3.json create mode 100644 pages/Operations - Job Drawing Status/widgets/Container1/Button3Copy3.json create mode 100644 pages/Operations - Unused Items/widgets/Container1/Button3Copy3.json create mode 100644 pages/Operations - WO Shortages/Operations - WO Shortages.json create mode 100644 pages/Operations - WO Shortages/queries/critical_parts/critical_parts.txt create mode 100644 pages/Operations - WO Shortages/queries/critical_parts/metadata.json create mode 100644 pages/Operations - WO Shortages/queries/wo_shortages/metadata.json create mode 100644 pages/Operations - WO Shortages/queries/wo_shortages/wo_shortages.txt create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button1.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button1Copy.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button1Copy2.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button2All.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button2Copy.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button2Copy2.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button3.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button3Copy.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button3Copy2.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Button3Copy3.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Container1.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Text1.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Text2.json create mode 100644 pages/Operations - WO Shortages/widgets/Container1/Text3.json create mode 100644 pages/Operations - WO Shortages/widgets/Heading.json create mode 100644 pages/Operations - WO Shortages/widgets/Tabs1/Tabs1.json create mode 100644 pages/Operations - WO Shortages/widgets/Tabs1/ws1tblparts.json create mode 100644 pages/Operations - WO Shortages/widgets/Tabs1/ws1tblshort.json create mode 100644 pages/Pending POs/widgets/Container1/Button3Copy3.json create mode 100644 pages/Pending Revisions/widgets/Container1/Button3Copy3.json create mode 100644 pages/Sales - Capacity Planning/widgets/Container1/Button3Copy3.json create mode 100644 pages/Sales - Units Ordered/widgets/Container1/Button3Copy3.json create mode 100644 pages/Sales - Units Shipped/widgets/Container1/Button3Copy3.json create mode 100644 pages/xGen/widgets/Container1/Button3Copy3.json diff --git a/.cursor/rules/no-docs-directory.mdc b/.cursor/rules/no-docs-directory.mdc new file mode 100644 index 0000000..e4a7d9d --- /dev/null +++ b/.cursor/rules/no-docs-directory.mdc @@ -0,0 +1,12 @@ +--- +description: Do not create or add to the /docs directory +alwaysApply: true +--- + +# No /docs Directory Changes + +Do **not** create new files under `docs/` or add to or modify any content in the `docs/` directory. + +- Do not create new files in `docs/`. +- Do not edit or append to existing files in `docs/`. +- When a task would normally involve documentation (e.g. README updates, changelog notes), skip any changes under `docs/` unless the user explicitly asks to change that directory. diff --git a/.cursor/skills/.DS_Store b/.cursor/skills/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..db9afceac404e1a5e811dcc1489bb270a7583241 GIT binary patch literal 8196 zcmeHMTWl0n7(U;$zzh>$s4Wy^*_9Qj(1ry{5mDTB+sLKmwk^GIS$22EcEWU~?9A>` zo0|IM1u*eNqoTeU^#L^sQJzecsEP4`AW>e#nE1qtH+(QL{%6i?v0TCvm&7^AoNvy5 z{{Ni)|NXyn`fnLyXe;Wg8LMZE$@Fm4RO)Wf_&k5EYf>PoCIs0t<}rgcF`N4Gb=sjI zj6fKHFalu&!U%*BxECTocQ$YGP40a$8}?xY!U+5?Bf!sx7(E=50Zs_?j}Gem5`d7G z0Dhx)ssntRSb)g@Cj|PYw5F&Y5SSt;F(AxoKE~AvlL1Z$6y^-VoFUj5L4^W;ce0E5 z>I_MNVIM{yjKK8~;Pjcp@~l8Ldv;3w-o!>oGRTZv*&WGQPL4FsUJC}S9#Co_LxRFqt&Jy+sm4^QSc2+-5`~Hy{4@@ z#lE`7844!jJ9xWY;ALMqA|9vW$k!lY;1MQdKzq4-#k99$aV2m8+T_8S;Mw- zOt^;Ncwn=xarOAE{j}UtO88louK0N|@KaW1s_LvrZ(skw-hCNOE4s#R!^(PQ$!O1d zhMUpmxa9VhykqYy7$tJIH}6=*VVh>==CfA5NXka5Wu?uhgm%8*Sk_L*H9ga@`};iO zh{xynLQ|&t23)7$rToSEOj=)P)$l!bjBsOjk5O<573JjY`Oya(n%1m;G}*DGYuEWX z+T0pdt((_dGF>xg8U0x|Z`itN5B8Kz-5VO9NZX$Iq-hisWiA@K?d-5|zNUV`!uuCR zB0}e?O=Xq7V$M8VG`&$RF3Vz%$db4sYp3?<f<1NUB|ywXY8FjXSfhw`0VxCqGhZvn&s|L)n6{qfE(IvFMa#|7#>UH|fw#&nfuP z*uEqZ>E}ze>bjwm(+let)I`#e9$7m_HkxUdOiF+H4g?G2cqRMd$ra)?{+V^jfAeD; zPbP%8R={M>ufYgQur{`p9UuaZvZL%}_7?k?onoiiS@tcvz%H?0*stt2_B*@6{zf%s zq85v=6wA?ohp+}~(To8GMc}a28+U0)E6L{DjN+6Ibw;q)0W=A}Jy*mX=Em(h6y%v`Kng+AQso2BeHM zBz%x61l&LAlm8-(65QgTAp!;mjdb$H1dKkhdCS&qo%bvOf0)fFcVBJYy!o-ED^@i> z{8;N`Vv0Pjr0#zy;j>^0cYGH3nR0Cg``lC)M|+4S#*wS!6lASXRGY!+ET8Av52*2| zHjRiRhRf83s5YHQB!(-aO;JrE?uua|x<=Kih?$CSyDr+KYLLZXgSt`Gs)?gwxJg~C zYD^J>wrG1)pf%@d{og2Asefl>hJGUA%{na0;jK2|o3i@D0Ah zMf`kCZg)-1ZTxZT+%BaHw&mD^q*-Ce)J5*Xs7mZv&+Gq=oB#fQ7d~0IWf*}l0(U9` zsBTZUx6y7lJDGT`9i!(UJ-l(f34y)|b$&Tc=$GS!lm9TJ_ZX>in`D3!0!c#cj{gvF UJO2O=@Bi@rzi|/queries//` +- **Files**: + - **`.txt`** -- Raw SQL. This is the **source of truth** for the query body. Keep it readable (real newlines, no escaping). + - **`metadata.json`** -- Appsmith action config. The `body` inside `unpublishedAction.actionConfiguration` must match the SQL in the `.txt` file, stored as a single-line string with `\n` for newlines and `\"` for double quotes. + +**Naming**: Use lowercase with underscores (e.g. `units_ordered_by_series`, `pending_pos_slx_pending`). The query name is the identifier used in bindings (e.g. `{{query_name.data}}`). + +## 2. Metadata.json required fields + +- **`gitSyncId`** (CRITICAL): Must be the **first field** in the root object. Format: `"<24-char-hex>_"`. Without this, Appsmith will auto-remove the query on the next pull/sync. Generate with: + ```bash + python3 -c "import uuid; print(uuid.uuid4().hex[:24] + '_' + str(uuid.uuid4()))" + ``` + Use the same 24-char hex prefix as the page's `gitSyncId` but a unique UUID suffix. +- **`id`**: `"_"` (e.g. `"Pending POs - SLx Pending_pending_pos_slx_pending"`). +- **`name`**: `""` (must match the folder and the name used in bindings). +- **`unpublishedAction.actionConfiguration.body`**: The exact SQL from `.txt`, escaped for JSON (newlines -> `\n`, `"` -> `\"`). +- **`unpublishedAction.datasource`**: Use the same as other queries on the page (e.g. `xTuple_Sandbox`). +- **`unpublishedAction.pageId`**: `""`. +- **`unpublishedAction.dynamicBindingPathList`**: `[{"key":"body"}]` when the body contains `{{...}}` widget references. +- Keep **`pluginId`**: `"postgres-plugin"`, **`pluginType`**: `"DB"`, and existing flags like **`encodeParamsToggle`**, **`paginationType`**, **`pluginSpecifiedTemplates`**, **`timeoutInMillisecond`** consistent with other DB queries in the app. + +When adding a new query, copy an existing query's `metadata.json` from the same page and change `gitSyncId`, `id`, `name`, and `body` (and ensure `body` stays in sync with the new `.txt` file). + +## 3. Wiring the query to a Table widget + +- In the Table widget JSON (e.g. `widgets/.../Table1.json`), set: + - **`tableData`**: `"{{.data}}"` +- Table columns read from the query result by **key**. The key is the **SELECT alias** from the query (e.g. `"Product"`, `"Qty Ordered"`). In `primaryColumns`, the column **id** can be a safe identifier (e.g. `Qty_Ordered`) while **originalId** / **alias** / **label** match the display name; **computedValue** must use the same key as in the query result, e.g. `currentRow["Qty Ordered"]`. + +So: **query SELECT aliases = keys in the table's row object**. Keep column keys and any `currentRow["..."]` references in the table in sync with those aliases. + +## 4. Parameter search syntax (widgets in SQL) + +### 4.1 Referencing a widget value + +- **Syntax**: `{{WidgetName.property}}` +- **Examples**: + - DatePicker date: `{{SeriesDateFrom.selectedDate}}`, `{{PowerDateTo.selectedDate}}` + - Other widgets: use the widget's value property (e.g. `selectedOptionValue`, `text`) as per Appsmith docs. + +Values are injected as strings. For PostgreSQL dates you typically cast in SQL, e.g. `'{{DatePicker1.selectedDate}}'::date`. + +### 4.2 Optional date range (no filter when either date is empty) + +Use this pattern so that: +- If **either** date widget is empty -> the date condition is **not** applied (all dates allowed). +- If **both** are set -> filter by `date_column BETWEEN from ::date AND to ::date`. + +**SQL pattern** (replace widget names and column as needed): + +```sql +AND ( + NULLIF('{{DateFromWidget.selectedDate}}','') IS NULL + OR NULLIF('{{DateToWidget.selectedDate}}','') IS NULL + OR date_column BETWEEN + NULLIF('{{DateFromWidget.selectedDate}}','')::date + AND NULLIF('{{DateToWidget.selectedDate}}','')::date +) +``` + +- **Logic**: `NULLIF('{{...}}','')` turns an empty string into SQL `NULL`. If either widget is empty, one of the first two conditions is true, so the whole `AND (...)` is true and the BETWEEN is not applied. When both are non-empty, the third branch applies the range. +- **Widget names**: Use the actual widget names (e.g. `SeriesDateFrom` / `SeriesDateTo` for one tab, `PowerDateFrom` / `PowerDateTo` for another). Ensure those widgets exist on the same page and (if in a tab) the same tab so the query runs with the right context. + +### 4.3 Required parameters (always filter) + +If the filter must always be applied (no "show all" when empty): + +- Use the binding directly and ensure the widget always has a value, or use a default in the widget. +- Example: `AND cohead_orderdate BETWEEN '{{DateFrom.selectedDate}}'::date AND '{{DateTo.selectedDate}}'::date` -- then empty dates may produce invalid SQL or empty results, so prefer the optional pattern above unless the UI guarantees non-empty values. + +## 5. Checklist when adding a new query + +1. Create `pages//queries//`. +2. Add `.txt` with the full SQL (source of truth). +3. Add `metadata.json` with correct `gitSyncId`, `id`, `name`, `body` (body = SQL from .txt, JSON-escaped), and same datasource/pageId as other page queries. +4. If the SQL uses `{{...}}`, set `dynamicBindingPathList` to `[{"key":"body"}]`. +5. In the Table widget that should show the data, set `tableData` to `{{.data}}`. +6. Ensure table column keys / `currentRow["..."]` match the query's SELECT aliases. + +## 6. Renaming or replacing a query + +- To **rename** (e.g. `units_shipped_by_series` -> `units_ordered_by_series`): + - Create the new folder and files under the new name (with a new `gitSyncId`). + - Update every reference to the old query (e.g. `tableData`: `{{old_name.data}}` -> `{{new_name.data}}`). + - Remove the old query folder (both `.txt` and `metadata.json`). +- When **replacing** the SQL but keeping the same name, update the `.txt` first, then update the `body` in `metadata.json` to match (same content, JSON-escaped). + +## Reference + +- **gitSyncId**: `<24-char-hex>_`. Without this, Appsmith auto-removes the query on sync. +- **Optional date range**: NULLIF + IS NULL + OR + BETWEEN as above. +- **Table data binding**: `{{.data}}`. +- **Column keys**: Must match query SELECT aliases (e.g. `"Product"`, `"Qty Ordered"`). diff --git a/.cursor/skills/add-tabs-to-page/SKILL.md b/.cursor/skills/add-tabs-to-page/SKILL.md new file mode 100644 index 0000000..c77efde --- /dev/null +++ b/.cursor/skills/add-tabs-to-page/SKILL.md @@ -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//widgets/Tabs1/ +├── Tabs1.json # The TABS_WIDGET itself +├── .json # Table inside tab 1 +├── .json # Table inside tab 2 +├── .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": "" + }, + "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": "