@fui-org/fui-cli 2.1.4 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +27 -7
  2. package/dist/fui-qrb1j9jz.js +397 -0
  3. package/dist/fui.js +1 -1
  4. package/package.json +1 -1
  5. package/skills/fui/SKILL.md +143 -38
  6. package/skills/fui/agents/fui-builder.md +62 -0
  7. package/skills/fui/agents/fui-tester.md +77 -0
  8. package/skills/{fui-skill → fui}/assets/projectdefaultstyle.css +489 -489
  9. package/skills/{fui-skill → fui}/examples/component.vue +162 -162
  10. package/skills/{fui-skill → fui}/references/INDEX.md +5 -4
  11. package/skills/{fui-skill → fui}/references/advanced-techniques.md +170 -168
  12. package/skills/{fui-skill → fui}/references/component-design.md +2 -2
  13. package/skills/{fui-skill → fui}/references/component-quickref.md +78 -78
  14. package/skills/{fui-skill → fui}/references/component-table.md +287 -287
  15. package/skills/{fui-skill → fui}/references/components-dialog.md +191 -191
  16. package/skills/{fui-skill → fui}/references/components-display.md +141 -141
  17. package/skills/{fui-skill → fui}/references/components-echart.md +316 -316
  18. package/skills/{fui-skill → fui}/references/components-input.md +335 -335
  19. package/skills/{fui-skill → fui}/references/controls-patterns.md +2 -2
  20. package/skills/{fui-skill → fui}/references/controls-styling-vocabulary.md +173 -173
  21. package/skills/{fui-skill → fui}/references/db-workflow.md +9 -9
  22. package/skills/{fui-skill → fui}/references/default-function.md +456 -456
  23. package/skills/{fui-skill → fui}/references/design-modes.md +57 -57
  24. package/skills/{fui-skill → fui}/references/echart-templates.md +489 -489
  25. package/skills/{fui-skill → fui}/references/fastproject.md +99 -99
  26. package/skills/{fui-skill → fui}/references/fullstack-workflow.md +314 -313
  27. package/skills/{fui-skill → fui}/references/module-data-patterns.md +117 -117
  28. package/skills/{fui-skill → fui}/references/module-json-anatomy.md +132 -132
  29. package/skills/{fui-skill → fui}/references/module-structure.md +144 -144
  30. package/skills/{fui-skill → fui}/references/new-session.md +1 -2
  31. package/skills/{fui-skill → fui}/references/pdfmake.md +60 -60
  32. package/skills/{fui-skill → fui}/references/permission-system.md +1 -1
  33. package/skills/{fui-skill → fui}/references/platform-architecture.md +269 -269
  34. package/skills/{fui-skill → fui}/references/project-config.md +303 -303
  35. package/skills/{fui-skill → fui}/references/project-provisioning.md +4 -4
  36. package/skills/{fui-skill → fui}/references/script-map.md +262 -262
  37. package/skills/{fui-skill → fui}/references/sql-clr-functions.md +225 -225
  38. package/skills/{fui-skill → fui}/references/system-design.md +2 -2
  39. package/skills/{fui-skill → fui}/references/tapi-permission-patterns.md +156 -156
  40. package/skills/{fui-skill → fui}/references/tapi-reference.md +474 -474
  41. package/skills/fui/references/team-run.md +141 -0
  42. package/skills/fui/references/tools-registry.md +211 -0
  43. package/skills/{fui-skill → fui}/references/ui-crosswindow-patterns.md +321 -321
  44. package/skills/{fui-skill → fui}/references/ui-dialog-patterns.md +255 -255
  45. package/skills/{fui-skill → fui}/references/ui-layout-patterns.md +176 -176
  46. package/skills/{fui-skill → fui}/references/ui-patterns.md +303 -303
  47. package/skills/{fui-skill → fui}/references/ui-screenshot-review.md +95 -95
  48. package/skills/{fui-skill → fui}/references/ui-table-cell-patterns.md +318 -318
  49. package/skills/{fui-skill → fui}/references/verification.md +11 -11
  50. package/skills/{fui-skill → fui}/references/watcher-patterns.md +163 -163
  51. package/skills/{fui-skill → fui}/references/websocket-realtime.md +271 -271
  52. package/skills/{fui-skill → fui}/scripts/component-3.0.js +2715 -2715
  53. package/skills/{fui-skill → fui}/scripts/component.js +3147 -3147
  54. package/skills/{fui-skill → fui}/scripts/componentTable-3.0.js +1038 -1038
  55. package/skills/{fui-skill → fui}/scripts/componentTable.js +891 -891
  56. package/skills/{fui-skill → fui}/scripts/defaultfunction-3.0.js +866 -866
  57. package/skills/{fui-skill → fui}/scripts/defaultfunction.js +1051 -1051
  58. package/skills/{fui-skill → fui}/scripts/fastproject-3.0.js +870 -870
  59. package/skills/{fui-skill → fui}/scripts/fastproject.js +828 -828
  60. package/dist/fui-h4z7zhk2.js +0 -381
  61. package/skills/fui-skill/SKILL.md +0 -139
  62. package/skills/fui-skill/references/tools-registry.md +0 -84
  63. /package/skills/{fui-skill → fui}/assets/projectdefaultstyle-3.0.css +0 -0
  64. /package/skills/{fui-skill → fui}/design-md/airbnb/DESIGN.md +0 -0
  65. /package/skills/{fui-skill → fui}/design-md/airbnb/README.md +0 -0
  66. /package/skills/{fui-skill → fui}/design-md/airtable/DESIGN.md +0 -0
  67. /package/skills/{fui-skill → fui}/design-md/airtable/README.md +0 -0
  68. /package/skills/{fui-skill → fui}/design-md/apple/DESIGN.md +0 -0
  69. /package/skills/{fui-skill → fui}/design-md/apple/README.md +0 -0
  70. /package/skills/{fui-skill → fui}/design-md/asu/DESIGN.md +0 -0
  71. /package/skills/{fui-skill → fui}/design-md/asu/README.md +0 -0
  72. /package/skills/{fui-skill → fui}/design-md/binance/DESIGN.md +0 -0
  73. /package/skills/{fui-skill → fui}/design-md/binance/README.md +0 -0
  74. /package/skills/{fui-skill → fui}/design-md/bmw/DESIGN.md +0 -0
  75. /package/skills/{fui-skill → fui}/design-md/bmw/README.md +0 -0
  76. /package/skills/{fui-skill → fui}/design-md/bmw-m/DESIGN.md +0 -0
  77. /package/skills/{fui-skill → fui}/design-md/bmw-m/README.md +0 -0
  78. /package/skills/{fui-skill → fui}/design-md/bugatti/DESIGN.md +0 -0
  79. /package/skills/{fui-skill → fui}/design-md/bugatti/README.md +0 -0
  80. /package/skills/{fui-skill → fui}/design-md/cal/DESIGN.md +0 -0
  81. /package/skills/{fui-skill → fui}/design-md/cal/README.md +0 -0
  82. /package/skills/{fui-skill → fui}/design-md/claude/DESIGN.md +0 -0
  83. /package/skills/{fui-skill → fui}/design-md/claude/README.md +0 -0
  84. /package/skills/{fui-skill → fui}/design-md/clay/DESIGN.md +0 -0
  85. /package/skills/{fui-skill → fui}/design-md/clay/README.md +0 -0
  86. /package/skills/{fui-skill → fui}/design-md/clickhouse/DESIGN.md +0 -0
  87. /package/skills/{fui-skill → fui}/design-md/clickhouse/README.md +0 -0
  88. /package/skills/{fui-skill → fui}/design-md/cohere/DESIGN.md +0 -0
  89. /package/skills/{fui-skill → fui}/design-md/cohere/README.md +0 -0
  90. /package/skills/{fui-skill → fui}/design-md/coinbase/DESIGN.md +0 -0
  91. /package/skills/{fui-skill → fui}/design-md/coinbase/README.md +0 -0
  92. /package/skills/{fui-skill → fui}/design-md/composio/DESIGN.md +0 -0
  93. /package/skills/{fui-skill → fui}/design-md/composio/README.md +0 -0
  94. /package/skills/{fui-skill → fui}/design-md/cursor/DESIGN.md +0 -0
  95. /package/skills/{fui-skill → fui}/design-md/cursor/README.md +0 -0
  96. /package/skills/{fui-skill → fui}/design-md/elevenlabs/DESIGN.md +0 -0
  97. /package/skills/{fui-skill → fui}/design-md/elevenlabs/README.md +0 -0
  98. /package/skills/{fui-skill → fui}/design-md/expo/DESIGN.md +0 -0
  99. /package/skills/{fui-skill → fui}/design-md/expo/README.md +0 -0
  100. /package/skills/{fui-skill → fui}/design-md/ferrari/DESIGN.md +0 -0
  101. /package/skills/{fui-skill → fui}/design-md/ferrari/README.md +0 -0
  102. /package/skills/{fui-skill → fui}/design-md/figma/DESIGN.md +0 -0
  103. /package/skills/{fui-skill → fui}/design-md/figma/README.md +0 -0
  104. /package/skills/{fui-skill → fui}/design-md/framer/DESIGN.md +0 -0
  105. /package/skills/{fui-skill → fui}/design-md/framer/README.md +0 -0
  106. /package/skills/{fui-skill → fui}/design-md/fui/DESIGN.md +0 -0
  107. /package/skills/{fui-skill → fui}/design-md/hashicorp/DESIGN.md +0 -0
  108. /package/skills/{fui-skill → fui}/design-md/hashicorp/README.md +0 -0
  109. /package/skills/{fui-skill → fui}/design-md/ibm/DESIGN.md +0 -0
  110. /package/skills/{fui-skill → fui}/design-md/ibm/README.md +0 -0
  111. /package/skills/{fui-skill → fui}/design-md/intercom/DESIGN.md +0 -0
  112. /package/skills/{fui-skill → fui}/design-md/intercom/README.md +0 -0
  113. /package/skills/{fui-skill → fui}/design-md/kraken/DESIGN.md +0 -0
  114. /package/skills/{fui-skill → fui}/design-md/kraken/README.md +0 -0
  115. /package/skills/{fui-skill → fui}/design-md/lamborghini/DESIGN.md +0 -0
  116. /package/skills/{fui-skill → fui}/design-md/lamborghini/README.md +0 -0
  117. /package/skills/{fui-skill → fui}/design-md/linear.app/DESIGN.md +0 -0
  118. /package/skills/{fui-skill → fui}/design-md/linear.app/README.md +0 -0
  119. /package/skills/{fui-skill → fui}/design-md/lovable/DESIGN.md +0 -0
  120. /package/skills/{fui-skill → fui}/design-md/lovable/README.md +0 -0
  121. /package/skills/{fui-skill → fui}/design-md/mastercard/DESIGN.md +0 -0
  122. /package/skills/{fui-skill → fui}/design-md/mastercard/README.md +0 -0
  123. /package/skills/{fui-skill → fui}/design-md/meta/DESIGN.md +0 -0
  124. /package/skills/{fui-skill → fui}/design-md/meta/README.md +0 -0
  125. /package/skills/{fui-skill → fui}/design-md/minimax/DESIGN.md +0 -0
  126. /package/skills/{fui-skill → fui}/design-md/minimax/README.md +0 -0
  127. /package/skills/{fui-skill → fui}/design-md/mintlify/DESIGN.md +0 -0
  128. /package/skills/{fui-skill → fui}/design-md/mintlify/README.md +0 -0
  129. /package/skills/{fui-skill → fui}/design-md/miro/DESIGN.md +0 -0
  130. /package/skills/{fui-skill → fui}/design-md/miro/README.md +0 -0
  131. /package/skills/{fui-skill → fui}/design-md/mistral.ai/DESIGN.md +0 -0
  132. /package/skills/{fui-skill → fui}/design-md/mistral.ai/README.md +0 -0
  133. /package/skills/{fui-skill → fui}/design-md/mongodb/DESIGN.md +0 -0
  134. /package/skills/{fui-skill → fui}/design-md/mongodb/README.md +0 -0
  135. /package/skills/{fui-skill → fui}/design-md/nike/DESIGN.md +0 -0
  136. /package/skills/{fui-skill → fui}/design-md/nike/README.md +0 -0
  137. /package/skills/{fui-skill → fui}/design-md/notion/DESIGN.md +0 -0
  138. /package/skills/{fui-skill → fui}/design-md/notion/README.md +0 -0
  139. /package/skills/{fui-skill → fui}/design-md/nvidia/DESIGN.md +0 -0
  140. /package/skills/{fui-skill → fui}/design-md/nvidia/README.md +0 -0
  141. /package/skills/{fui-skill → fui}/design-md/ollama/DESIGN.md +0 -0
  142. /package/skills/{fui-skill → fui}/design-md/ollama/README.md +0 -0
  143. /package/skills/{fui-skill → fui}/design-md/opencode.ai/DESIGN.md +0 -0
  144. /package/skills/{fui-skill → fui}/design-md/opencode.ai/README.md +0 -0
  145. /package/skills/{fui-skill → fui}/design-md/pinterest/DESIGN.md +0 -0
  146. /package/skills/{fui-skill → fui}/design-md/pinterest/README.md +0 -0
  147. /package/skills/{fui-skill → fui}/design-md/playstation/DESIGN.md +0 -0
  148. /package/skills/{fui-skill → fui}/design-md/playstation/README.md +0 -0
  149. /package/skills/{fui-skill → fui}/design-md/posthog/DESIGN.md +0 -0
  150. /package/skills/{fui-skill → fui}/design-md/posthog/README.md +0 -0
  151. /package/skills/{fui-skill → fui}/design-md/raycast/DESIGN.md +0 -0
  152. /package/skills/{fui-skill → fui}/design-md/raycast/README.md +0 -0
  153. /package/skills/{fui-skill → fui}/design-md/renault/DESIGN.md +0 -0
  154. /package/skills/{fui-skill → fui}/design-md/renault/README.md +0 -0
  155. /package/skills/{fui-skill → fui}/design-md/replicate/DESIGN.md +0 -0
  156. /package/skills/{fui-skill → fui}/design-md/replicate/README.md +0 -0
  157. /package/skills/{fui-skill → fui}/design-md/resend/DESIGN.md +0 -0
  158. /package/skills/{fui-skill → fui}/design-md/resend/README.md +0 -0
  159. /package/skills/{fui-skill → fui}/design-md/revolut/DESIGN.md +0 -0
  160. /package/skills/{fui-skill → fui}/design-md/revolut/README.md +0 -0
  161. /package/skills/{fui-skill → fui}/design-md/runwayml/DESIGN.md +0 -0
  162. /package/skills/{fui-skill → fui}/design-md/runwayml/README.md +0 -0
  163. /package/skills/{fui-skill → fui}/design-md/sanity/DESIGN.md +0 -0
  164. /package/skills/{fui-skill → fui}/design-md/sanity/README.md +0 -0
  165. /package/skills/{fui-skill → fui}/design-md/sentry/DESIGN.md +0 -0
  166. /package/skills/{fui-skill → fui}/design-md/sentry/README.md +0 -0
  167. /package/skills/{fui-skill → fui}/design-md/shopify/DESIGN.md +0 -0
  168. /package/skills/{fui-skill → fui}/design-md/shopify/README.md +0 -0
  169. /package/skills/{fui-skill → fui}/design-md/slack/DESIGN.md +0 -0
  170. /package/skills/{fui-skill → fui}/design-md/spacex/DESIGN.md +0 -0
  171. /package/skills/{fui-skill → fui}/design-md/spacex/README.md +0 -0
  172. /package/skills/{fui-skill → fui}/design-md/spotify/DESIGN.md +0 -0
  173. /package/skills/{fui-skill → fui}/design-md/spotify/README.md +0 -0
  174. /package/skills/{fui-skill → fui}/design-md/starbucks/DESIGN.md +0 -0
  175. /package/skills/{fui-skill → fui}/design-md/starbucks/README.md +0 -0
  176. /package/skills/{fui-skill → fui}/design-md/stripe/DESIGN.md +0 -0
  177. /package/skills/{fui-skill → fui}/design-md/stripe/README.md +0 -0
  178. /package/skills/{fui-skill → fui}/design-md/supabase/DESIGN.md +0 -0
  179. /package/skills/{fui-skill → fui}/design-md/supabase/README.md +0 -0
  180. /package/skills/{fui-skill → fui}/design-md/superhuman/DESIGN.md +0 -0
  181. /package/skills/{fui-skill → fui}/design-md/superhuman/README.md +0 -0
  182. /package/skills/{fui-skill → fui}/design-md/tesla/DESIGN.md +0 -0
  183. /package/skills/{fui-skill → fui}/design-md/tesla/README.md +0 -0
  184. /package/skills/{fui-skill → fui}/design-md/theverge/DESIGN.md +0 -0
  185. /package/skills/{fui-skill → fui}/design-md/theverge/README.md +0 -0
  186. /package/skills/{fui-skill → fui}/design-md/together.ai/DESIGN.md +0 -0
  187. /package/skills/{fui-skill → fui}/design-md/together.ai/README.md +0 -0
  188. /package/skills/{fui-skill → fui}/design-md/uber/DESIGN.md +0 -0
  189. /package/skills/{fui-skill → fui}/design-md/uber/README.md +0 -0
  190. /package/skills/{fui-skill → fui}/design-md/vercel/DESIGN.md +0 -0
  191. /package/skills/{fui-skill → fui}/design-md/vercel/README.md +0 -0
  192. /package/skills/{fui-skill → fui}/design-md/vodafone/DESIGN.md +0 -0
  193. /package/skills/{fui-skill → fui}/design-md/vodafone/README.md +0 -0
  194. /package/skills/{fui-skill → fui}/design-md/voltagent/DESIGN.md +0 -0
  195. /package/skills/{fui-skill → fui}/design-md/voltagent/README.md +0 -0
  196. /package/skills/{fui-skill → fui}/design-md/warp/DESIGN.md +0 -0
  197. /package/skills/{fui-skill → fui}/design-md/warp/README.md +0 -0
  198. /package/skills/{fui-skill → fui}/design-md/webflow/DESIGN.md +0 -0
  199. /package/skills/{fui-skill → fui}/design-md/webflow/README.md +0 -0
  200. /package/skills/{fui-skill → fui}/design-md/wired/DESIGN.md +0 -0
  201. /package/skills/{fui-skill → fui}/design-md/wired/README.md +0 -0
  202. /package/skills/{fui-skill → fui}/design-md/wise/DESIGN.md +0 -0
  203. /package/skills/{fui-skill → fui}/design-md/wise/README.md +0 -0
  204. /package/skills/{fui-skill → fui}/design-md/x.ai/DESIGN.md +0 -0
  205. /package/skills/{fui-skill → fui}/design-md/x.ai/README.md +0 -0
  206. /package/skills/{fui-skill → fui}/design-md/zapier/DESIGN.md +0 -0
  207. /package/skills/{fui-skill → fui}/design-md/zapier/README.md +0 -0
  208. /package/skills/{fui-skill → fui}/examples/f-table-patterns.json +0 -0
  209. /package/skills/{fui-skill → fui}/examples/module-patterns.json +0 -0
  210. /package/skills/{fui-skill → fui}/examples/project-patterns.json +0 -0
  211. /package/skills/{fui-skill → fui}/references/coding-standards.md +0 -0
  212. /package/skills/{fui-skill → fui}/references/db-table-design.md +0 -0
  213. /package/skills/{fui-skill → fui}/references/fsheet.md +0 -0
  214. /package/skills/{fui-skill → fui}/references/tapi-file-api.md +0 -0
  215. /package/skills/{fui-skill → fui}/references/ui-templates.md +0 -0
  216. /package/skills/{fui-skill → fui}/scripts/fechart.js +0 -0
  217. /package/skills/{fui-skill → fui}/scripts/fsheet.js +0 -0
@@ -1,287 +1,287 @@
1
- # FUI Table Components (Source-Based)
2
-
3
- > Owns: **`f-table` in depth: all props, auto-header, `sum-format`, Excel export (`excel`), readonly table**. In-cell controls (`t-*`): [ui-table-cell-patterns.md](ui-table-cell-patterns.md).
4
-
5
- Source: `scripts/componentTable.js` (V2), `scripts/componentTable-3.0.js` (V3).
6
-
7
- > **Check the version before writing headers** — V2/V3 field names differ; wrong names = no render. See "V2 vs V3" below.
8
-
9
- > `f-table` does CRUD and readonly. Readonly = no `:update-api` (or `:disabled-update true`); omit `:headers` to auto-build.
10
-
11
- ## Prefix Reminder
12
-
13
- `f-table` = FUI component · `t-*` = FUI cell renderers · `v-data-table` = Vuetify internals (CRUD/action flow comes from FUI wrappers).
14
-
15
- ## f-table
16
-
17
- ### Props (exact from source)
18
-
19
- `value`, `headers`, `updateForm`, `updateFormAttr`, `updateApi`, `items`, `label`, `excel`, `showSearch`, `sumFormat`, `disabledHover`, `returnObject`, `itemKey`, `itemsPerPage`, `disabledUpdate`, `hideDefaultFooter`, `template`, `headercomponent`, `height`, `fixedHeader`, `fixedCols`
20
-
21
- ### Real Behavior
22
-
23
- 1. Header item with `el` → custom slot renderer created automatically.
24
- 2. `updateApi.new` or `updateApi['new-action']` present → add button appears.
25
- 3. CRUD action slots rely on header `value: "ctrl-update"`.
26
- 4. Edit dialog is `f-dialog` with `updateForm`.
27
- 5. Selection emit: `returnObject=false` → array of `itemKey`; `returnObject=true` → selected row objects.
28
- 6. Row events from slots: `click:row`, `mouseover:row`, `mouseleave:row`.
29
- 7. **Auto headers**: no `:headers` → built from `items` (see Auto Headers).
30
- 8. **Sum row / sum line**: `sum-format` takes a string OR object — rendered differently (see sum-format).
31
- 9. **Excel button**: `excel` = `true` exports the raw data, `"UI"` exports exactly what the table shows (see Excel export).
32
-
33
- ### Fixed left columns (`fixed-cols`)
34
-
35
- `fixed-cols="N"` keeps the first N columns during horizontal scroll. V2 and V3. Omitted, `0`, or invalid → normal behavior.
36
-
37
- ```json
38
- {
39
- "el": "f-table",
40
- "w": 12,
41
- "attr": {
42
- ":items": "dsNhanVien",
43
- "fixed-cols": "2",
44
- ":headers": [
45
- { "text": "Mã", "value": "Ma", "width": "100px" },
46
- { "text": "Họ tên", "value": "HoTen", "width": "220px" },
47
- { "text": "Phòng ban", "value": "PhongBan", "width": "180px" }
48
- ]
49
- }
50
- }
51
- ```
52
-
53
- - Set `width` on each fixed column; missing → `150px`.
54
- - Numeric `width` = pixels; CSS strings like `"120px"` kept as-is.
55
- - `fixed-cols` counts actual rendered columns; Vuetify select/group columns, if enabled, are counted.
56
- - CSS is scoped per `f-table` instance, so several tables with different fixed counts can share a page.
57
-
58
- ### Auto Headers (no `:headers`)
59
-
60
- With `:headers` omitted, `f-table` builds headers from the first row of `items` (via `buildHeader`):
61
-
62
- - Each key → column `{ text: key, value: key, divider: true }`; numeric values → `el: "t-num"` (right-aligned number format).
63
- - `:update-api` with `edit`/`delete` and no `disabled-update` → `ctrl-update` column appended.
64
- - Rebuilt whenever `items` changes shape (watch on `items`) — safe with async loads.
65
-
66
- **Use when** columns = API fields exactly (SP aliases nice Vietnamese names). **Declare `:headers` when** labels differ from fields, columns hidden, custom `t-*` (t-time, t-check, t-link...), specific width/align, or technical fields (internal IDs, flags) must not show.
67
-
68
- ```json
69
- { "el": "f-table", "w": "12", "attr": { "label": "Kết quả", ":items": "dsKetQua", "show-search": true } }
70
- ```
71
-
72
- ### sum-format — column totals (2 forms)
73
-
74
- **Form 1 — string template → ONE total text line below the table** (in `v-card-actions`, NOT inside `v-data-table`, not column-aligned):
75
-
76
- ```json
77
- "sum-format": "Tổng đăng ký: [[DangKy]] | Nhập học: [[NhapHoc]]"
78
- ```
79
-
80
- - `[[FieldName]]` → `_.sumBy(items, 'FieldName')`, fixed format `'0,0'` (numeral).
81
- - Single right-aligned text line below the table (separated by `v-divider`).
82
- - For one summary line / several totals in one sentence.
83
- - Static string → attr WITHOUT `:` prefix.
84
-
85
- **Form 2 — object → REAL total row inside the table** (`v-data-table` slot `body.append`, aligned per column):
86
-
87
- ```json
88
- ":sum-format": { "DangKy": "0,0", "NhapHoc": "0,0", "HocPhi": "0,0.0" }
89
- ```
90
-
91
- - Key = column `value` in headers; value = numeral format for that column (`"0,0"`, `"0,0.0"`, `"0.0%"`, ...).
92
- - Renders a final `<tr>`: cells whose column key is in the object show `_.sumBy(items, key)` in that format; others empty. All cells right-aligned, `font-weight-medium`.
93
- - Aligned with columns, scrolls with the body.
94
- - Object → attr WITH `:` prefix.
95
-
96
- **Choosing:** "total row under each column / total row in table / column-aligned totals" → object. "General summary text line" → string. **NEVER build a total row manually** (no pushing a "Tổng" row into items, no hand-written body.append template) — use `sum-format`.
97
-
98
- Full object example:
99
-
100
- ```json
101
- {
102
- "el": "f-table",
103
- "w": "12",
104
- "attr": {
105
- "label": "Thống kê tuyển sinh",
106
- ":items": "dsThongKe",
107
- ":headers": [
108
- { "text": "Ngành", "value": "TenNganh" },
109
- { "text": "Đăng ký", "value": "DangKy", "el": "t-num" },
110
- { "text": "Nhập học", "value": "NhapHoc", "el": "t-num" }
111
- ],
112
- ":sum-format": { "DangKy": "0,0", "NhapHoc": "0,0" }
113
- }
114
- }
115
- ```
116
-
117
- ### Excel export — prop `excel` (2 modes)
118
-
119
- Excel button on the table toolbar, in both V2 and V3. **V2**: no `excel` → no button. **V3**: the prop defaults to `true`, so the button always shows; hide it with `":excel": false`.
120
-
121
- | Value | Rows | Columns & headers | Cell values |
122
- | -------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
123
- | `true` _(default, old behavior)_ | **All** `items`, original order — ignores search and sorting | Every field of the **first row**, header = raw field name (`HoTen`), including IDs/technical fields not shown | Raw, dates as ISO strings |
124
- | `"UI"` | Only rows **matching the search box**, in the **current sort order** (including `group-by`), **all pages** | Only columns declared in `headers`, same order, header = `title`/`text` (HTML stripped) | **Raw, unformatted data** — as `true` |
125
-
126
- ```json
127
- { "el": "f-table", "w": 12, "attr": { "label": "Danh sách", ":items": "dsHang", "excel": "UI", ":headers": [ ... ] } }
128
- ```
129
-
130
- - `"UI"` is a static string → **no** `:` prefix. Case-insensitive (`"ui"` works). Any other value (`true`, `"true"`, …) is the raw mode.
131
- - **In short:** `"UI"` takes **rows and columns as displayed**, while **cell values stay raw**. User wants "export what I see / filtered / sorted" → `"UI"`. Need every field dumped (IDs, hidden fields) → `true`.
132
-
133
- **`"UI"` details:**
134
-
135
- - **Filtering** reuses the table's own `customSearch`, so the result matches the rows on screen. **Sorting** follows the current `v-data-table` sort state (caught via `update:options`). If the user has not clicked a column, the `sort-by`/`sort-desc` (V2) or `sort-by` (V3) from attr apply. Strings compare with `Intl.Collator`, so accented Vietnamese sorts correctly.
136
- - **Pagination does not matter**: the file always holds every page of the filtered set. Collapsed groups (`group-by`) are exported too.
137
- - **Dropped columns**: `ctrl-update`, system columns (`data-table-select`/`-group`/`-expand`), headers without `key`/`value`. Group headers (`children`, V3) are flattened to `Parent / Child`.
138
- - **Column field from `v-model`**: a header with `attr["v-model"]: "item.X"` exports field `X`, even when the column `key` is another name. Filtering and sorting still use `key`/`value`, as `v-data-table` does.
139
- - **Cell value = raw data, NOT formatted**: numbers export as plain numbers (no numFmt, zeros not hidden even with `t-num` `hidezero`), dates as the raw API string (`2026-08-15T00:00:00`, not dd/mm/yyyy), `t-html` as the raw HTML string, `t-select`/`t-check` as the raw code/value, not the label. `format` of `t-num`/`t-time` is **not** applied to the file.
140
- - **No total row**: `sum-format` only displays on the table; the file has no total line.
141
- - Need a pre-formatted file (numFmt, real dates, a `SUM` total row) → call `jsonToExcel` with `columns` yourself; see [default-function.md](default-function.md).
142
- - Only the file presentation is adjusted: header row bold and frozen. Column width comes from the header's px `width` (÷7), else estimated from content. File/sheet name = `label` with HTML stripped (sheet cut to 31 characters), `tableFP` when there is no label.
143
- - No matching rows → "Không có dòng nào đang hiển thị để xuất Excel", no empty file.
144
- - **Selected** rows (show-select) do not matter: `"UI"` exports every displayed row, not only the selected ones.
145
-
146
- ### updateApi Keys Used By Source
147
-
148
- `new`, `edit`, `delete`, `default-item`, `new-action`, `edit-action`, `data-out`
149
-
150
- ### Minimal CRUD Example
151
-
152
- ```json
153
- {
154
- "el": "f-table",
155
- "w": "12",
156
- "attr": {
157
- "label": "Danh sach nhan vien",
158
- ":items": "items",
159
- "item-key": "ID",
160
- ":return-object": false,
161
- "show-search": true,
162
- "excel": true,
163
- ":headers": [
164
- { "text": "Ho ten", "value": "FullName" },
165
- { "text": "Email", "value": "Email" },
166
- { "text": "Trang thai", "value": "Active", "el": "t-boolean", "align": "center", "width": "90px" },
167
- { "text": "Tac vu", "value": "ctrl-update", "align": "center", "width": "120px" }
168
- ],
169
- ":update-form": [
170
- { "el": "v-text-field", "attr": { "v-model": "FullName", "label": "Ho ten", ":required": true } },
171
- { "el": "v-text-field", "attr": { "v-model": "Email", "label": "Email", ":required": true } }
172
- ],
173
- ":update-api": {
174
- "default-item": { "FullName": "", "Email": "", "Active": true },
175
- "new": {
176
- "API": "/api/staff/create",
177
- "IN": { "FullName": "item.FullName", "Email": "item.Email", "Active": "item.Active" },
178
- "CALLBACK": { "CALL": "apiLoadItems" }
179
- },
180
- "edit": {
181
- "API": "/api/staff/update",
182
- "IN": { "ID": "item.ID", "FullName": "item.FullName", "Email": "item.Email", "Active": "item.Active" },
183
- "CALLBACK": { "CALL": "apiLoadItems" }
184
- },
185
- "delete": {
186
- "CONFIRM": "Xac nhan xoa?",
187
- "API": "/api/staff/delete",
188
- "IN": { "ID": "item.ID" },
189
- "CALLBACK": { "CALL": "apiLoadItems" }
190
- }
191
- }
192
- }
193
- }
194
- ```
195
-
196
- ## Readonly Table
197
-
198
- `f-table` without `:update-api` (no add/edit/delete buttons); omit `:headers` to auto-build:
199
-
200
- ```json
201
- {
202
- "el": "f-table",
203
- "w": "12",
204
- "attr": {
205
- "label": "Danh sach xem nhanh",
206
- ":items": "items",
207
- "item-key": "ID",
208
- "show-search": true,
209
- "excel": true,
210
- ":items-per-page": 20
211
- }
212
- }
213
- ```
214
-
215
- ## t-* Cell Renderers
216
-
217
- Set `el` in `headers`:
218
-
219
- ```json
220
- { "text": "Ngay tao", "value": "CreatedAt", "el": "t-time", "attr": { "format": "DD/MM/YYYY HH:mm" } }
221
- ```
222
-
223
- Supported: `t-html`, `t-label`, `t-num`, `t-time`, `t-boolean`, `t-check`, `t-text`, `t-select`, `t-combobox`, `t-menu`, `t-link`, `t-button`
224
-
225
- ### Notes
226
-
227
- 1. Editable renderers (`t-check`, `t-text`, `t-select`, `t-combobox`, `t-menu`, `t-button`) use `tableActionEvent`.
228
- 2. Opening a dialog from a row action: `target: "dialog"` plus `wid`, `url`, `title`, `onclose`.
229
- 3. Literal-string rule applies only in Action `IN` mapping. Static props without `:` are plain strings by default.
230
-
231
- ---
232
-
233
- ## V2 vs V3 (source: `componentTable.js` vs `componentTable-3.0.js`)
234
-
235
- Framework version comes from `_projectInfo.json` or `_moduleInfo.json` — check before writing headers.
236
-
237
- ### Header field names
238
-
239
- **Most common mistake.** V2 `text`/`value` are renamed `title`/`key` in V3:
240
-
241
- | Field | V2 (`componentTable.js`) | V3 (`componentTable-3.0.js`) |
242
- | ------------ | ------------------------ | ---------------------------- |
243
- | Column label | `"text": "Tên cột"` | `"title": "Tên cột"` |
244
- | Data field | `"value": "TenField"` | `"key": "TenField"` |
245
- | Ctrl column | `"value": "ctrl-update"` | `"key": "ctrl-update"` |
246
-
247
- ```json
248
- // V2 ✅
249
- { "text": "Họ tên", "value": "HoTen", "el": "t-label" }
250
- { "text": "Tác vụ", "value": "ctrl-update", "align": "center" }
251
-
252
- // V3 ✅
253
- { "title": "Họ tên", "key": "HoTen", "el": "t-label" }
254
- { "title": "Tác vụ", "key": "ctrl-update", "align": "center" }
255
- ```
256
-
257
- > `text`/`value` on V3 → columns render but **headers vanish, sort breaks** (Vuetify 3 ignores them). No JS error — only visible in a rendered screenshot.
258
-
259
- ### `t-*` columns in V3 must declare `v-model`
260
-
261
- V2 adds `v-model="item['{value}']"` to every column with `el` by itself. **V3 does not**: a `t-num`/`t-time`/`t-label`… column without `attr["v-model"]` gets an empty value. `t-num` then gets 0, which `hidezero` hides, so **the whole column is blank with no error anywhere** (measured on the real V3 framework). Headers the table builds itself (no `:headers`) already have `v-model`.
262
-
263
- ```json
264
- // V3 ✅
265
- { "title": "Số lượng", "key": "SoLuong", "el": "t-num", "attr": { "v-model": "item.SoLuong" } }
266
- ```
267
-
268
- ### Extra V3 props
269
-
270
- | Prop | V3 default | Note |
271
- | ---------------- | ----------- | ------------------------------------------------------------------------ |
272
- | `density` | `'compact'` | Row density — `'compact'`/`'comfortable'`/`'default'` |
273
- | `disable-sort` | `false` | Disable sorting for the whole table |
274
- | `slots` | `null` | Dynamic custom slots (object `{ name, html }`) |
275
- | `group-template` | `null` | Template for groups (`groupBy`) |
276
- | `height` | `null` | Same as V2: null/omitted = auto-grow; ≤0 = fill to bottom; >0 = fixed px |
277
-
278
- ### sum-format object in V3
279
-
280
- In V3 the `sum-format` object keys must match header `key` (not `value`):
281
-
282
- ```json
283
- // V3 ✅ — key = "DangKy" khớp với header { "key": "DangKy" }
284
- ":sum-format": { "DangKy": "0,0", "NhapHoc": "0,0" }
285
- ```
286
-
287
- Otherwise identical to V2.
1
+ # FUI Table Components (Source-Based)
2
+
3
+ > Owns: **`f-table` in depth: all props, auto-header, `sum-format`, Excel export (`excel`), readonly table**. In-cell controls (`t-*`): [ui-table-cell-patterns.md](ui-table-cell-patterns.md).
4
+
5
+ Source: `scripts/componentTable.js` (V2), `scripts/componentTable-3.0.js` (V3).
6
+
7
+ > **Check the version before writing headers** — V2/V3 field names differ; wrong names = no render. See "V2 vs V3" below.
8
+
9
+ > `f-table` does CRUD and readonly. Readonly = no `:update-api` (or `:disabled-update true`); omit `:headers` to auto-build.
10
+
11
+ ## Prefix Reminder
12
+
13
+ `f-table` = FUI component · `t-*` = FUI cell renderers · `v-data-table` = Vuetify internals (CRUD/action flow comes from FUI wrappers).
14
+
15
+ ## f-table
16
+
17
+ ### Props (exact from source)
18
+
19
+ `value`, `headers`, `updateForm`, `updateFormAttr`, `updateApi`, `items`, `label`, `excel`, `showSearch`, `sumFormat`, `disabledHover`, `returnObject`, `itemKey`, `itemsPerPage`, `disabledUpdate`, `hideDefaultFooter`, `template`, `headercomponent`, `height`, `fixedHeader`, `fixedCols`
20
+
21
+ ### Real Behavior
22
+
23
+ 1. Header item with `el` → custom slot renderer created automatically.
24
+ 2. `updateApi.new` or `updateApi['new-action']` present → add button appears.
25
+ 3. CRUD action slots rely on header `value: "ctrl-update"`.
26
+ 4. Edit dialog is `f-dialog` with `updateForm`.
27
+ 5. Selection emit: `returnObject=false` → array of `itemKey`; `returnObject=true` → selected row objects.
28
+ 6. Row events from slots: `click:row`, `mouseover:row`, `mouseleave:row`.
29
+ 7. **Auto headers**: no `:headers` → built from `items` (see Auto Headers).
30
+ 8. **Sum row / sum line**: `sum-format` takes a string OR object — rendered differently (see sum-format).
31
+ 9. **Excel button**: `excel` = `true` exports the raw data, `"UI"` exports exactly what the table shows (see Excel export).
32
+
33
+ ### Fixed left columns (`fixed-cols`)
34
+
35
+ `fixed-cols="N"` keeps the first N columns during horizontal scroll. V2 and V3. Omitted, `0`, or invalid → normal behavior.
36
+
37
+ ```json
38
+ {
39
+ "el": "f-table",
40
+ "w": 12,
41
+ "attr": {
42
+ ":items": "dsNhanVien",
43
+ "fixed-cols": "2",
44
+ ":headers": [
45
+ { "text": "Mã", "value": "Ma", "width": "100px" },
46
+ { "text": "Họ tên", "value": "HoTen", "width": "220px" },
47
+ { "text": "Phòng ban", "value": "PhongBan", "width": "180px" }
48
+ ]
49
+ }
50
+ }
51
+ ```
52
+
53
+ - Set `width` on each fixed column; missing → `150px`.
54
+ - Numeric `width` = pixels; CSS strings like `"120px"` kept as-is.
55
+ - `fixed-cols` counts actual rendered columns; Vuetify select/group columns, if enabled, are counted.
56
+ - CSS is scoped per `f-table` instance, so several tables with different fixed counts can share a page.
57
+
58
+ ### Auto Headers (no `:headers`)
59
+
60
+ With `:headers` omitted, `f-table` builds headers from the first row of `items` (via `buildHeader`):
61
+
62
+ - Each key → column `{ text: key, value: key, divider: true }`; numeric values → `el: "t-num"` (right-aligned number format).
63
+ - `:update-api` with `edit`/`delete` and no `disabled-update` → `ctrl-update` column appended.
64
+ - Rebuilt whenever `items` changes shape (watch on `items`) — safe with async loads.
65
+
66
+ **Use when** columns = API fields exactly (SP aliases nice Vietnamese names). **Declare `:headers` when** labels differ from fields, columns hidden, custom `t-*` (t-time, t-check, t-link...), specific width/align, or technical fields (internal IDs, flags) must not show.
67
+
68
+ ```json
69
+ { "el": "f-table", "w": "12", "attr": { "label": "Kết quả", ":items": "dsKetQua", "show-search": true } }
70
+ ```
71
+
72
+ ### sum-format — column totals (2 forms)
73
+
74
+ **Form 1 — string template → ONE total text line below the table** (in `v-card-actions`, NOT inside `v-data-table`, not column-aligned):
75
+
76
+ ```json
77
+ "sum-format": "Tổng đăng ký: [[DangKy]] | Nhập học: [[NhapHoc]]"
78
+ ```
79
+
80
+ - `[[FieldName]]` → `_.sumBy(items, 'FieldName')`, fixed format `'0,0'` (numeral).
81
+ - Single right-aligned text line below the table (separated by `v-divider`).
82
+ - For one summary line / several totals in one sentence.
83
+ - Static string → attr WITHOUT `:` prefix.
84
+
85
+ **Form 2 — object → REAL total row inside the table** (`v-data-table` slot `body.append`, aligned per column):
86
+
87
+ ```json
88
+ ":sum-format": { "DangKy": "0,0", "NhapHoc": "0,0", "HocPhi": "0,0.0" }
89
+ ```
90
+
91
+ - Key = column `value` in headers; value = numeral format for that column (`"0,0"`, `"0,0.0"`, `"0.0%"`, ...).
92
+ - Renders a final `<tr>`: cells whose column key is in the object show `_.sumBy(items, key)` in that format; others empty. All cells right-aligned, `font-weight-medium`.
93
+ - Aligned with columns, scrolls with the body.
94
+ - Object → attr WITH `:` prefix.
95
+
96
+ **Choosing:** "total row under each column / total row in table / column-aligned totals" → object. "General summary text line" → string. **NEVER build a total row manually** (no pushing a "Tổng" row into items, no hand-written body.append template) — use `sum-format`.
97
+
98
+ Full object example:
99
+
100
+ ```json
101
+ {
102
+ "el": "f-table",
103
+ "w": "12",
104
+ "attr": {
105
+ "label": "Thống kê tuyển sinh",
106
+ ":items": "dsThongKe",
107
+ ":headers": [
108
+ { "text": "Ngành", "value": "TenNganh" },
109
+ { "text": "Đăng ký", "value": "DangKy", "el": "t-num" },
110
+ { "text": "Nhập học", "value": "NhapHoc", "el": "t-num" }
111
+ ],
112
+ ":sum-format": { "DangKy": "0,0", "NhapHoc": "0,0" }
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### Excel export — prop `excel` (2 modes)
118
+
119
+ Excel button on the table toolbar, in both V2 and V3. **V2**: no `excel` → no button. **V3**: the prop defaults to `true`, so the button always shows; hide it with `":excel": false`.
120
+
121
+ | Value | Rows | Columns & headers | Cell values |
122
+ | -------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
123
+ | `true` _(default, old behavior)_ | **All** `items`, original order — ignores search and sorting | Every field of the **first row**, header = raw field name (`HoTen`), including IDs/technical fields not shown | Raw, dates as ISO strings |
124
+ | `"UI"` | Only rows **matching the search box**, in the **current sort order** (including `group-by`), **all pages** | Only columns declared in `headers`, same order, header = `title`/`text` (HTML stripped) | **Raw, unformatted data** — as `true` |
125
+
126
+ ```json
127
+ { "el": "f-table", "w": 12, "attr": { "label": "Danh sách", ":items": "dsHang", "excel": "UI", ":headers": [ ... ] } }
128
+ ```
129
+
130
+ - `"UI"` is a static string → **no** `:` prefix. Case-insensitive (`"ui"` works). Any other value (`true`, `"true"`, …) is the raw mode.
131
+ - **In short:** `"UI"` takes **rows and columns as displayed**, while **cell values stay raw**. User wants "export what I see / filtered / sorted" → `"UI"`. Need every field dumped (IDs, hidden fields) → `true`.
132
+
133
+ **`"UI"` details:**
134
+
135
+ - **Filtering** reuses the table's own `customSearch`, so the result matches the rows on screen. **Sorting** follows the current `v-data-table` sort state (caught via `update:options`). If the user has not clicked a column, the `sort-by`/`sort-desc` (V2) or `sort-by` (V3) from attr apply. Strings compare with `Intl.Collator`, so accented Vietnamese sorts correctly.
136
+ - **Pagination does not matter**: the file always holds every page of the filtered set. Collapsed groups (`group-by`) are exported too.
137
+ - **Dropped columns**: `ctrl-update`, system columns (`data-table-select`/`-group`/`-expand`), headers without `key`/`value`. Group headers (`children`, V3) are flattened to `Parent / Child`.
138
+ - **Column field from `v-model`**: a header with `attr["v-model"]: "item.X"` exports field `X`, even when the column `key` is another name. Filtering and sorting still use `key`/`value`, as `v-data-table` does.
139
+ - **Cell value = raw data, NOT formatted**: numbers export as plain numbers (no numFmt, zeros not hidden even with `t-num` `hidezero`), dates as the raw API string (`2026-08-15T00:00:00`, not dd/mm/yyyy), `t-html` as the raw HTML string, `t-select`/`t-check` as the raw code/value, not the label. `format` of `t-num`/`t-time` is **not** applied to the file.
140
+ - **No total row**: `sum-format` only displays on the table; the file has no total line.
141
+ - Need a pre-formatted file (numFmt, real dates, a `SUM` total row) → call `jsonToExcel` with `columns` yourself; see [default-function.md](default-function.md).
142
+ - Only the file presentation is adjusted: header row bold and frozen. Column width comes from the header's px `width` (÷7), else estimated from content. File/sheet name = `label` with HTML stripped (sheet cut to 31 characters), `tableFP` when there is no label.
143
+ - No matching rows → "Không có dòng nào đang hiển thị để xuất Excel", no empty file.
144
+ - **Selected** rows (show-select) do not matter: `"UI"` exports every displayed row, not only the selected ones.
145
+
146
+ ### updateApi Keys Used By Source
147
+
148
+ `new`, `edit`, `delete`, `default-item`, `new-action`, `edit-action`, `data-out`
149
+
150
+ ### Minimal CRUD Example
151
+
152
+ ```json
153
+ {
154
+ "el": "f-table",
155
+ "w": "12",
156
+ "attr": {
157
+ "label": "Danh sach nhan vien",
158
+ ":items": "items",
159
+ "item-key": "ID",
160
+ ":return-object": false,
161
+ "show-search": true,
162
+ "excel": true,
163
+ ":headers": [
164
+ { "text": "Ho ten", "value": "FullName" },
165
+ { "text": "Email", "value": "Email" },
166
+ { "text": "Trang thai", "value": "Active", "el": "t-boolean", "align": "center", "width": "90px" },
167
+ { "text": "Tac vu", "value": "ctrl-update", "align": "center", "width": "120px" }
168
+ ],
169
+ ":update-form": [
170
+ { "el": "v-text-field", "attr": { "v-model": "FullName", "label": "Ho ten", ":required": true } },
171
+ { "el": "v-text-field", "attr": { "v-model": "Email", "label": "Email", ":required": true } }
172
+ ],
173
+ ":update-api": {
174
+ "default-item": { "FullName": "", "Email": "", "Active": true },
175
+ "new": {
176
+ "API": "/api/staff/create",
177
+ "IN": { "FullName": "item.FullName", "Email": "item.Email", "Active": "item.Active" },
178
+ "CALLBACK": { "CALL": "apiLoadItems" }
179
+ },
180
+ "edit": {
181
+ "API": "/api/staff/update",
182
+ "IN": { "ID": "item.ID", "FullName": "item.FullName", "Email": "item.Email", "Active": "item.Active" },
183
+ "CALLBACK": { "CALL": "apiLoadItems" }
184
+ },
185
+ "delete": {
186
+ "CONFIRM": "Xac nhan xoa?",
187
+ "API": "/api/staff/delete",
188
+ "IN": { "ID": "item.ID" },
189
+ "CALLBACK": { "CALL": "apiLoadItems" }
190
+ }
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
196
+ ## Readonly Table
197
+
198
+ `f-table` without `:update-api` (no add/edit/delete buttons); omit `:headers` to auto-build:
199
+
200
+ ```json
201
+ {
202
+ "el": "f-table",
203
+ "w": "12",
204
+ "attr": {
205
+ "label": "Danh sach xem nhanh",
206
+ ":items": "items",
207
+ "item-key": "ID",
208
+ "show-search": true,
209
+ "excel": true,
210
+ ":items-per-page": 20
211
+ }
212
+ }
213
+ ```
214
+
215
+ ## t-* Cell Renderers
216
+
217
+ Set `el` in `headers`:
218
+
219
+ ```json
220
+ { "text": "Ngay tao", "value": "CreatedAt", "el": "t-time", "attr": { "format": "DD/MM/YYYY HH:mm" } }
221
+ ```
222
+
223
+ Supported: `t-html`, `t-label`, `t-num`, `t-time`, `t-boolean`, `t-check`, `t-text`, `t-select`, `t-combobox`, `t-menu`, `t-link`, `t-button`
224
+
225
+ ### Notes
226
+
227
+ 1. Editable renderers (`t-check`, `t-text`, `t-select`, `t-combobox`, `t-menu`, `t-button`) use `tableActionEvent`.
228
+ 2. Opening a dialog from a row action: `target: "dialog"` plus `wid`, `url`, `title`, `onclose`.
229
+ 3. Literal-string rule applies only in Action `IN` mapping. Static props without `:` are plain strings by default.
230
+
231
+ ---
232
+
233
+ ## V2 vs V3 (source: `componentTable.js` vs `componentTable-3.0.js`)
234
+
235
+ Framework version comes from `_projectInfo.json` or `_moduleInfo.json` — check before writing headers.
236
+
237
+ ### Header field names
238
+
239
+ **Most common mistake.** V2 `text`/`value` are renamed `title`/`key` in V3:
240
+
241
+ | Field | V2 (`componentTable.js`) | V3 (`componentTable-3.0.js`) |
242
+ | ------------ | ------------------------ | ---------------------------- |
243
+ | Column label | `"text": "Tên cột"` | `"title": "Tên cột"` |
244
+ | Data field | `"value": "TenField"` | `"key": "TenField"` |
245
+ | Ctrl column | `"value": "ctrl-update"` | `"key": "ctrl-update"` |
246
+
247
+ ```json
248
+ // V2 ✅
249
+ { "text": "Họ tên", "value": "HoTen", "el": "t-label" }
250
+ { "text": "Tác vụ", "value": "ctrl-update", "align": "center" }
251
+
252
+ // V3 ✅
253
+ { "title": "Họ tên", "key": "HoTen", "el": "t-label" }
254
+ { "title": "Tác vụ", "key": "ctrl-update", "align": "center" }
255
+ ```
256
+
257
+ > `text`/`value` on V3 → columns render but **headers vanish, sort breaks** (Vuetify 3 ignores them). No JS error — only visible in a rendered screenshot.
258
+
259
+ ### `t-*` columns in V3 must declare `v-model`
260
+
261
+ V2 adds `v-model="item['{value}']"` to every column with `el` by itself. **V3 does not**: a `t-num`/`t-time`/`t-label`… column without `attr["v-model"]` gets an empty value. `t-num` then gets 0, which `hidezero` hides, so **the whole column is blank with no error anywhere** (measured on the real V3 framework). Headers the table builds itself (no `:headers`) already have `v-model`.
262
+
263
+ ```json
264
+ // V3 ✅
265
+ { "title": "Số lượng", "key": "SoLuong", "el": "t-num", "attr": { "v-model": "item.SoLuong" } }
266
+ ```
267
+
268
+ ### Extra V3 props
269
+
270
+ | Prop | V3 default | Note |
271
+ | ---------------- | ----------- | ------------------------------------------------------------------------ |
272
+ | `density` | `'compact'` | Row density — `'compact'`/`'comfortable'`/`'default'` |
273
+ | `disable-sort` | `false` | Disable sorting for the whole table |
274
+ | `slots` | `null` | Dynamic custom slots (object `{ name, html }`) |
275
+ | `group-template` | `null` | Template for groups (`groupBy`) |
276
+ | `height` | `null` | Same as V2: null/omitted = auto-grow; ≤0 = fill to bottom; >0 = fixed px |
277
+
278
+ ### sum-format object in V3
279
+
280
+ In V3 the `sum-format` object keys must match header `key` (not `value`):
281
+
282
+ ```json
283
+ // V3 ✅ — key = "DangKy" khớp với header { "key": "DangKy" }
284
+ ":sum-format": { "DangKy": "0,0", "NhapHoc": "0,0" }
285
+ ```
286
+
287
+ Otherwise identical to V2.