@fui-org/fui-cli 1.3.2 → 2.0.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 (111) hide show
  1. package/README.md +19 -3
  2. package/dist/fui-bmg5pnmq.js +379 -0
  3. package/dist/fui.js +1 -1
  4. package/package.json +3 -6
  5. package/skills/fui/SKILL.md +9 -41
  6. package/skills/fui-skill/SKILL.md +95 -225
  7. package/skills/fui-skill/assets/projectdefaultstyle-3.0.css +555 -0
  8. package/skills/fui-skill/assets/projectdefaultstyle.css +207 -235
  9. package/skills/fui-skill/references/INDEX.md +105 -137
  10. package/skills/fui-skill/references/advanced-techniques.md +76 -68
  11. package/skills/fui-skill/references/coding-standards.md +56 -56
  12. package/skills/fui-skill/references/component-design.md +166 -173
  13. package/skills/fui-skill/references/component-quickref.md +61 -60
  14. package/skills/fui-skill/references/component-table.md +128 -117
  15. package/skills/fui-skill/references/components-dialog.md +55 -56
  16. package/skills/fui-skill/references/components-display.md +24 -30
  17. package/skills/fui-skill/references/components-echart.md +186 -261
  18. package/skills/fui-skill/references/components-input.md +72 -96
  19. package/skills/fui-skill/references/controls-patterns.md +196 -342
  20. package/skills/fui-skill/references/controls-styling-vocabulary.md +130 -97
  21. package/skills/fui-skill/references/db-table-design.md +24 -28
  22. package/skills/fui-skill/references/db-workflow.md +191 -390
  23. package/skills/fui-skill/references/default-function.md +169 -128
  24. package/skills/fui-skill/references/design-modes.md +35 -63
  25. package/skills/fui-skill/references/echart-templates.md +204 -196
  26. package/skills/fui-skill/references/fastproject.md +62 -60
  27. package/skills/fui-skill/references/fsheet.md +109 -124
  28. package/skills/fui-skill/references/fullstack-workflow.md +90 -128
  29. package/skills/fui-skill/references/module-data-patterns.md +31 -40
  30. package/skills/fui-skill/references/module-json-anatomy.md +47 -52
  31. package/skills/fui-skill/references/module-structure.md +80 -196
  32. package/skills/fui-skill/references/new-session.md +49 -51
  33. package/skills/fui-skill/references/pdfmake.md +17 -17
  34. package/skills/fui-skill/references/permission-system.md +89 -108
  35. package/skills/fui-skill/references/platform-architecture.md +128 -153
  36. package/skills/fui-skill/references/project-config.md +102 -134
  37. package/skills/fui-skill/references/project-provisioning.md +139 -244
  38. package/skills/fui-skill/references/script-map.md +208 -242
  39. package/skills/fui-skill/references/sql-clr-functions.md +98 -97
  40. package/skills/fui-skill/references/system-design.md +63 -88
  41. package/skills/fui-skill/references/tapi-file-api.md +46 -52
  42. package/skills/fui-skill/references/tapi-permission-patterns.md +51 -53
  43. package/skills/fui-skill/references/tapi-reference.md +132 -207
  44. package/skills/fui-skill/references/tools-registry.md +84 -460
  45. package/skills/fui-skill/references/ui-crosswindow-patterns.md +79 -75
  46. package/skills/fui-skill/references/ui-dialog-patterns.md +98 -72
  47. package/skills/fui-skill/references/ui-layout-patterns.md +26 -26
  48. package/skills/fui-skill/references/ui-patterns.md +71 -83
  49. package/skills/fui-skill/references/ui-screenshot-review.md +63 -62
  50. package/skills/fui-skill/references/ui-table-cell-patterns.md +61 -59
  51. package/skills/fui-skill/references/ui-templates.md +16 -23
  52. package/skills/fui-skill/references/verification.md +236 -246
  53. package/skills/fui-skill/references/watcher-patterns.md +30 -63
  54. package/skills/fui-skill/references/websocket-realtime.md +83 -66
  55. package/skills/fui-skill/scripts/component-3.0.js +298 -131
  56. package/skills/fui-skill/scripts/component.js +277 -271
  57. package/skills/fui-skill/scripts/componentTable-3.0.js +182 -53
  58. package/skills/fui-skill/scripts/componentTable.js +171 -49
  59. package/skills/fui-skill/scripts/defaultfunction-3.0.js +88 -3
  60. package/skills/fui-skill/scripts/defaultfunction.js +88 -3
  61. package/skills/fui-skill/scripts/fsheet.js +38 -0
  62. package/dist/fui-y8an39cn.js +0 -420
  63. package/skills/fui-skill/README.md +0 -112
  64. package/skills/fui-skill/metadata.json +0 -75
  65. package/skills/fui-skill-cli/SKILL.md +0 -139
  66. package/skills/fui-skill-cli/references/INDEX.md +0 -110
  67. package/skills/fui-skill-cli/references/advanced-techniques.md +0 -168
  68. package/skills/fui-skill-cli/references/coding-standards.md +0 -112
  69. package/skills/fui-skill-cli/references/component-design.md +0 -448
  70. package/skills/fui-skill-cli/references/component-quickref.md +0 -78
  71. package/skills/fui-skill-cli/references/component-table.md +0 -248
  72. package/skills/fui-skill-cli/references/components-dialog.md +0 -191
  73. package/skills/fui-skill-cli/references/components-display.md +0 -141
  74. package/skills/fui-skill-cli/references/components-echart.md +0 -316
  75. package/skills/fui-skill-cli/references/components-input.md +0 -335
  76. package/skills/fui-skill-cli/references/controls-patterns.md +0 -701
  77. package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +0 -137
  78. package/skills/fui-skill-cli/references/db-table-design.md +0 -73
  79. package/skills/fui-skill-cli/references/db-workflow.md +0 -288
  80. package/skills/fui-skill-cli/references/default-function.md +0 -425
  81. package/skills/fui-skill-cli/references/design-modes.md +0 -57
  82. package/skills/fui-skill-cli/references/echart-templates.md +0 -489
  83. package/skills/fui-skill-cli/references/fastproject.md +0 -99
  84. package/skills/fui-skill-cli/references/fsheet.md +0 -203
  85. package/skills/fui-skill-cli/references/fullstack-workflow.md +0 -313
  86. package/skills/fui-skill-cli/references/module-data-patterns.md +0 -117
  87. package/skills/fui-skill-cli/references/module-json-anatomy.md +0 -132
  88. package/skills/fui-skill-cli/references/module-structure.md +0 -141
  89. package/skills/fui-skill-cli/references/new-session.md +0 -85
  90. package/skills/fui-skill-cli/references/pdfmake.md +0 -60
  91. package/skills/fui-skill-cli/references/permission-system.md +0 -150
  92. package/skills/fui-skill-cli/references/platform-architecture.md +0 -269
  93. package/skills/fui-skill-cli/references/project-config.md +0 -303
  94. package/skills/fui-skill-cli/references/project-provisioning.md +0 -278
  95. package/skills/fui-skill-cli/references/script-map.md +0 -262
  96. package/skills/fui-skill-cli/references/sql-clr-functions.md +0 -225
  97. package/skills/fui-skill-cli/references/system-design.md +0 -89
  98. package/skills/fui-skill-cli/references/tapi-file-api.md +0 -185
  99. package/skills/fui-skill-cli/references/tapi-permission-patterns.md +0 -156
  100. package/skills/fui-skill-cli/references/tapi-reference.md +0 -474
  101. package/skills/fui-skill-cli/references/tools-registry.md +0 -84
  102. package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +0 -321
  103. package/skills/fui-skill-cli/references/ui-dialog-patterns.md +0 -255
  104. package/skills/fui-skill-cli/references/ui-layout-patterns.md +0 -176
  105. package/skills/fui-skill-cli/references/ui-patterns.md +0 -303
  106. package/skills/fui-skill-cli/references/ui-screenshot-review.md +0 -95
  107. package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +0 -318
  108. package/skills/fui-skill-cli/references/ui-templates.md +0 -22
  109. package/skills/fui-skill-cli/references/verification.md +0 -236
  110. package/skills/fui-skill-cli/references/watcher-patterns.md +0 -163
  111. package/skills/fui-skill-cli/references/websocket-realtime.md +0 -271
@@ -1,97 +1,99 @@
1
1
  # FastProject (Core Framework)
2
2
 
3
- > File này sở hữu: **bootstrapper `fastproject.js`: vòng đời khởi tạo project/module**. Bản đồ toàn bộ script xem [script-map.md](script-map.md).
3
+ > Owns: **the `fastproject.js` bootstrapper: project/module init lifecycle**. Full script map: [script-map.md](script-map.md).
4
4
 
5
- This document references the core framework logic defined in `fastproject.js`. This script is responsible for initializing the application, building the UI from JSON configurations, and handling the logic engine.
5
+ `fastproject.js` initializes the app, builds UI from JSON config, and runs the logic engine.
6
6
 
7
7
  ## Global State
8
8
 
9
9
  ### vueData
10
- The central reactive state object for the application.
11
- - `p_domain`, `p_routers`, `p_params`: Routing and environment info.
12
- - `user`: Current user information.
13
- - `v_Set`: Application settings (title, menu, attributes).
14
- - `v_Loading`: Global loading state.
10
+
11
+ Central reactive state.
12
+
13
+ - `p_domain`, `p_routers`, `p_params`: routing and environment.
14
+ - `user`: current user.
15
+ - `v_Set`: app settings (title, menu, attributes).
16
+ - `v_Loading`: global loading state.
15
17
 
16
18
  ### vueOBJ
17
- The Vue instance configuration object.
19
+
20
+ Vue instance config object.
18
21
 
19
22
  ## Core Functions
20
23
 
21
24
  ### loadModuleInfo()
22
- Initializes the module by:
23
- 1. Loading project and module settings.
24
- 2. Building URL parameters.
25
- 3. Fetching user info (if configured).
26
- 4. Calling `createModuleDom()`.
25
+
26
+ 1. Load project and module settings.
27
+ 2. Build URL parameters.
28
+ 3. Fetch user info (if configured).
29
+ 4. Call `createModuleDom()`.
27
30
 
28
31
  ### createModuleDom()
29
- Builds the Vue application structure:
30
- 1. Executes initial data actions (`$moduleUI.data`).
31
- 2. Generates the HTML structure using `buildModuleUI`.
32
- 3. Initializes Vue watchers and WebSocket connections.
33
- 4. Mounts the Vue instance.
32
+
33
+ 1. Run initial data actions (`$moduleUI.data`).
34
+ 2. Generate HTML via `buildModuleUI`.
35
+ 3. Init Vue watchers and WebSocket connections.
36
+ 4. Mount the Vue instance.
34
37
 
35
38
  ### buildModuleUI(controlsList, target)
36
- Recursively builds the DOM structure from a JSON list of controls.
37
- - **Parameters:**
38
- - `controlsList` (Array): List of control definitions.
39
- - `target` (Object): Target object for data binding (usually `vueData`).
40
- - **Returns:** jQuery object containing the generated HTML.
39
+
40
+ Recursively builds DOM from a JSON control list.
41
+
42
+ - `controlsList` (Array): control definitions.
43
+ - `target` (Object): binding target (usually `vueData`).
44
+ - Returns: jQuery object with the generated HTML.
41
45
 
42
46
  ### buildControl(controlsList, flex, target)
43
- Helper function for `buildModuleUI` to generate individual controls.
44
- - Supports grid layout (`v-layout`, `v-flex`) and nested components.
45
- - Handles attribute binding and event mapping.
47
+
48
+ `buildModuleUI` helper for individual controls.
49
+
50
+ - Grid layout (`v-layout`, `v-flex`) and nested components.
51
+ - Attribute binding and event mapping.
46
52
 
47
53
  ### runAction(obj, includeData)
48
- The central logic engine. Executes a sequence of actions defined in JSON.
49
- - **Parameters:**
50
- - `obj` (Object|Array): The action(s) to execute.
51
- - `includeData` (Object): Context data.
52
- - **Supported Action Types:**
53
- - `CONFIRM`: Show confirmation dialog.
54
- - `API`: Call an API.
55
- - `CALL`: Recursive call to another action.
56
- - `EXE`: Execute raw JavaScript.
57
- - `FUN`: Call a global function.
58
- - `IF/THEN/ELSE`: Conditional logic.
59
- - `MESS`/`MESSBOX`: Show messages.
60
- - `IN`/`OUT`: Data mapping.
54
+
55
+ Central logic engine; runs a JSON action sequence.
56
+
57
+ - `obj` (Object|Array): action(s).
58
+ - `includeData` (Object): context data.
59
+ - Action types: `CONFIRM` (confirm dialog), `API` (call API), `CALL` (another action, recursive), `EXE` (raw JS), `FUN` (global function), `IF/THEN/ELSE`, `MESS`/`MESSBOX` (messages), `IN`/`OUT` (data mapping).
61
60
 
62
61
  ### vueAction(objAction, includeData, callBack)
63
- Internal function used by `runAction` to execute a single action step.
62
+
63
+ Internal; runs a single action step for `runAction`.
64
64
 
65
65
  ### callAPI(objApi, callBack, includeData)
66
- Executes an API call defined in a JSON action object.
67
- - Handles `BEFORE`, `AFTER` actions, and success/error callbacks.
68
- - Automatically maps response data to `vueData` if `OUT` is specified.
66
+
67
+ Runs an API call from a JSON action.
68
+
69
+ - Handles `BEFORE`, `AFTER` actions, success/error callbacks.
70
+ - Maps response into `vueData` when `OUT` is set.
69
71
 
70
72
  ### mapData(map, target, src, includeData)
71
- Maps data from a source object to a target object based on a mapping definition.
72
- - Supports cross-window data mapping using `#PARENT` or window IDs.
73
- - **Parameters:**
74
- - `map` (Object|String): Mapping definition.
75
- - `target` (Object): Destination object.
76
- - `src` (Object): Source object.
73
+
74
+ Maps source → target by a mapping definition.
75
+
76
+ - Cross-window mapping via `#PARENT` or window IDs.
77
+ - `map` (Object|String): mapping; `target` (Object): destination; `src` (Object): source.
77
78
 
78
79
  ### getVueData(key, src)
79
- Resolves a value from a key string, supporting deep paths and template syntax (e.g., `{{user.Name}}`).
80
+
81
+ Resolves a value from a key string; deep paths and template syntax (e.g. `{{user.Name}}`).
80
82
 
81
83
  ### bindData(obj)
82
- Resolves all dynamic values within an object.
84
+
85
+ Resolves all dynamic values in an object.
83
86
 
84
87
  ### createWatch(obj)
85
- Sets up Vue watchers based on a JSON configuration.
86
- - Supports deep watching.
87
- - Executes actions when watched values change.
88
+
89
+ Vue watchers from JSON config; supports deep watch; runs actions on change.
88
90
 
89
91
  ### loginFUN()
90
- Handles user login redirection.
91
- - Opens a login window or redirects to the login page.
92
+
93
+ Login redirect: opens a login window or redirects to the login page.
92
94
 
93
95
  ### errorMess(code, message)
94
- Displays standard error messages based on HTTP status codes.
95
- - **Parameters:**
96
- - `code` (String|Number): HTTP status code.
97
- - `message` (String): Custom error message.
96
+
97
+ Standard error message by HTTP status.
98
+
99
+ - `code` (String|Number): HTTP status; `message` (String): custom message.
@@ -1,183 +1,168 @@
1
- # f-sheet — Bảng tính nhập liệu (dựa trên AG Grid)
1
+ # f-sheet — Data-Entry Grid (AG Grid)
2
2
 
3
- > File này sở hữu: **`f-sheet` (AG Grid): import, props, cấu hình cột, cell renderer dựng sẵn, chọn vùng/fill/clipboard, wiring API**.
3
+ > Owns: **`f-sheet` (AG Grid): import, props, columns, built-in cell renderers, range select/fill/clipboard, API wiring**.
4
4
 
5
- `f-sheet` là **data grid nhập liệu kiểu Excel**, xây dựng **hoàn toàn trên AG Grid Community**. Mọi cấu hình cột/lưới đều là cấu hình AG Grid — **khi cần tuỳ biến, dựa vào kiến thức AG Grid** (https://www.ag-grid.com/, phiên bản Community). FUI chỉ bọc thêm: toolbar (thêm/xoá/lưu/xuất Excel), theo dõi dòng thay đổi, chọn vùng nhiều ô kiểu Excel + fill handle + copy/paste TSV, và wiring API.
5
+ Excel-style entry grid, **entirely AG Grid Community** — customize with AG Grid knowledge (https://www.ag-grid.com/, Community). FUI adds: toolbar (add/delete/save/export Excel), change tracking, range select + fill handle + TSV copy/paste, API wiring.
6
6
 
7
- > **Phân biệt với `f-table`**: `f-table` hiển thị dữ liệu dạng datatable, sửa dòng qua dialog form riêng. `f-sheet` sửa **ngay trên ô của bảng** (không qua dialog), phù hợp nhập liệu hàng loạt kiểu Excel (dán nhiều dòng, sửa nhanh nhiều ô). Cùng là component có sẵn — không tự viết bảng nhập liệu bằng `uc-*.vue`.
7
+ > **vs `f-table`** (rows edited via dialog form): `f-sheet` edits **in cells**, for bulk entry (paste rows, fast multi-cell edits). Never hand-write an entry grid as `uc-*.vue`.
8
8
 
9
- > Nền tảng (nguồn sự thật): `scripts/fsheet.js` — **một file DUY NHẤT dùng chung cho cả V2 và V3** (không còn khái niệm 2 bản phải giữ đồng bộ). Component khai cả `beforeDestroy` (Vue 2) lẫn `beforeUnmount` (Vue 3) để hoạt động đúng trên cả hai runtime. Template dùng thuần class/prop **Vuetify 3** (`variant="text"`, `density="comfortable"`, `text-primary`, `text-subtitle-1`, `text-caption`) — trên module V2 (Vuetify 2) toolbar vẫn bấm được nhưng có thể không lên đúng màu/kiểu vì Vuetify 2 bỏ qua các prop/class lạ này. Dùng `ajaxCALL`/`confirm`/`jsonToExcel` bên trong — khớp [component-design.md](component-design.md) §12.
9
+ > Source: `scripts/fsheet.js`, **one file for V2 and V3** (`beforeDestroy` + `beforeUnmount`). Template uses **Vuetify 3** props/classes (`variant="text"`, `density="comfortable"`, `text-primary`, `text-subtitle-1`, `text-caption`); on V2 the toolbar works but styling may be off. Uses `ajaxCALL`/`confirm`/`jsonToExcel` ([component-design.md](component-design.md) §12).
10
10
 
11
11
  ---
12
12
 
13
- ## Import BẮT BUỘC (thiếu → không dùng được f-sheet)
13
+ ## Required import
14
14
 
15
- Thêm đúng 2 file, **đúng thứ tự**, qua `file_import_new`:
15
+ Exactly 2 files, **in this order**, via `fui import new` (missing → unusable):
16
16
 
17
17
  ```
18
18
  /include/ag-grid/ag-grid-community.min.js
19
19
  /include/ag-grid/fsheet.js
20
20
  ```
21
21
 
22
- Quy trình khi thêm/bỏ f-sheet: `file_import_list` (projectId + moduleId) → chưa có thì `file_import_new` từng file → bỏ f-sheet mà không module/component khác dùng thì `file_import_delete`. Xem [component-quickref.md](component-quickref.md).
22
+ `fui import list` (projectId + moduleId) → `fui import new` missing ones; removing f-sheet and nothing else uses them → `fui import delete`. See [component-quickref.md](component-quickref.md).
23
23
 
24
24
  ---
25
25
 
26
26
  ## Props
27
27
 
28
- | Prop (kebab-case trong `attr`) | Kiểu | Ý nghĩa |
29
- |---|---|---|
30
- | `label` | string | Tiêu đề + hiện trên thanh công cụ |
31
- | `:columns` | array | **colDefs của AG Grid** (xem mục Cột). Bỏ trống → tự suy cột từ `items[0]` (mỗi field → `{ field, headerName: field }`) |
32
- | `:items` | array | Dữ liệu dòng (rowData) |
33
- | `row-key` | string | Tên field khóa chính (mặc định `id`) — dùng để nhận diện dòng đổi/mới/xoá |
34
- | `:height` | number | Bỏ trống = tự giãn theo số dòng; `<=0` = fill tới đáy màn hình chừa `abs(height)` px; `>0` = px cố định (xem mục Chiều cao) |
35
- | `:allow-add` | bool | Hiện nút Thêm dòng |
36
- | `:allow-delete` | bool | Hiện cột checkbox chọn + nút Xoá |
37
- | `readonly` | bool | Khoá toàn bộ chỉnh sửa (đồng thời tắt luôn fill handle) |
38
- | `update-api` | string | Endpoint tAPI **lưu từng dòng đã đổi** (gọi qua `ajaxCALL`, tự prepend `apiDomain`). Không khai thì nút Lưu bị `disabled` |
39
- | `delete-api` | string | Endpoint tAPI xoá một dòng |
40
- | `:save-extra` | object | Tham số phụ **trộn vào payload** mỗi lần lưu/xoá (vd `{ LopID: vueData.lopID }`) — đi qua `mapData`, hỗ trợ tham chiếu động `vueData.x` |
41
- | `:default-item` | object | Mẫu giá trị cho dòng mới khi bấm Thêm — cũng đi qua `mapData`, hỗ trợ tham chiếu động `vueData.x`, không chỉ literal tĩnh |
28
+ | Prop (kebab-case in `attr`) | Type | Meaning |
29
+ | --------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------ |
30
+ | `label` | string | Title, shown on toolbar |
31
+ | `:columns` | array | **AG Grid colDefs**. Empty → inferred from `items[0]` (`{ field, headerName: field }`) |
32
+ | `:items` | array | rowData |
33
+ | `row-key` | string | PK field (default `id`) — tracks changed/new/deleted rows |
34
+ | `:height` | number | empty = auto-grow; `<=0` = fill to screen bottom leaving `abs(height)` px; `>0` = fixed px |
35
+ | `:allow-add` | bool | Add row button |
36
+ | `:allow-delete` | bool | Checkbox column + Delete button |
37
+ | `readonly` | bool | Lock all editing (also fill handle) |
38
+ | `update-api` | string | tAPI endpoint **saving each changed row** (`ajaxCALL`, auto-prepends `apiDomain`). Missing → Save `disabled` |
39
+ | `delete-api` | string | tAPI endpoint deleting one row |
40
+ | `:save-extra` | object | Extra params **merged into every save/delete payload** (e.g. `{ LopID: vueData.lopID }`); via `mapData`, dynamic `vueData.x` allowed |
41
+ | `:default-item` | object | New-row template on Add; via `mapData`, dynamic `vueData.x` allowed (not only literals) |
42
42
 
43
43
  ---
44
44
 
45
- ## Cột (`columns`) = colDef của AG Grid
45
+ ## Columns = AG Grid colDef
46
46
 
47
- Mỗi phần tử `columns` là một **AG Grid colDef** — dùng trực tiếp kiến thức AG Grid:
47
+ | Key | Meaning |
48
+ | ----------------------------------- | ----------------------------------------------------------------------------------------------- |
49
+ | `field` / `headerName` | data field / label |
50
+ | `editable` | **on by default** (`defaultColDef.editable: !readonly`); read-only column → `"editable": false` |
51
+ | `width` / `minWidth` / `flex` | default `flex:1`, `minWidth:80` |
52
+ | `pinned` | `'left'`/`'right'` |
53
+ | `sortable` / `filter` / `resizable` | all on by default |
54
+ | `valueFormatter` / `valueGetter` | AG Grid functions |
55
+ | `cellEditor` | AG Grid built-in name (e.g. `agDateStringCellEditor`) or own global function name |
56
+ | `cellRenderer` | one of 4 f-sheet built-ins (below) or own global function name |
57
+ | `type` | **registered columnType name — only `"number"` / `"date"`**, not a free data type |
48
58
 
49
- | Key colDef hay dùng | Ý nghĩa |
50
- |---|---|
51
- | `field` | Tên field trong dòng dữ liệu |
52
- | `headerName` | Nhãn cột |
53
- | `editable` | Cho sửa ô — **mặc định bật** (f-sheet đặt `editable: !readonly` trong `defaultColDef`); cột chỉ đọc khai `"editable": false` |
54
- | `width` / `minWidth` / `flex` | Bề rộng cột (mặc định `flex:1`, `minWidth:80`) |
55
- | `pinned` | `'left'`/`'right'` cố định cột |
56
- | `sortable` / `filter` / `resizable` | Mặc định đều bật |
57
- | `valueFormatter` / `valueGetter` | Định dạng/hiển thị (hàm AG Grid) |
58
- | `cellEditor` | Editor tuỳ chỉnh — tên có sẵn của AG Grid (vd `agDateStringCellEditor`) hoặc tên hàm global tự viết |
59
- | `cellRenderer` | Renderer tuỳ chỉnh — 4 renderer dựng sẵn của f-sheet (xem bên dưới) hoặc tên hàm global tự viết |
60
- | `type` | **Tên columnType đã đăng ký** — chỉ có `"number"` và `"date"`. **KHÔNG phải** chỗ khai kiểu dữ liệu tự do (xem mục columnType ngay dưới) |
59
+ Other features (cellClassRules, aggregation...) → AG Grid docs; colDef passes straight through.
61
60
 
62
- > Các tính năng cột nâng cao khác (cellClassRules, aggregation...) tra tài liệu AG Grid Community — f-sheet truyền colDef thẳng vào grid. Riêng cột `type:"number"`/`type:"date"` được f-sheet **tự gắn** `cellStyle`/`valueFormatter`/`valueParser`/`filter` (xem bên dưới) — chỉ khai tay khi cần logic khác mặc định.
61
+ ### columnType: only `number` and `date`
63
62
 
64
- ### columnType — f-sheet đăng ký ĐÚNG 2 tên: `number` và `date`
63
+ `type` references `gridOptions.columnTypes`; only two registered (`FS_COLUMN_TYPES`, top of `scripts/fsheet.js`). Unregistered name (`"date_time"`, `"currency"`, `"text"`, `"string"`, `"boolean"`…):
65
64
 
66
- `type` **không phải** key gốc của AG Grid dùng để khai kiểu dữ liệu. Nó là **tên tham chiếu tới một columnType đã đăng ký** trong `gridOptions.columnTypes`. f-sheet đăng ký đúng hai tên — bảng `FS_COLUMN_TYPES` ở đầu `scripts/fsheet.js` là nguồn duy nhất.
65
+ - AG Grid **ignores it** (logs only `warning #36 colDef.type 'x' does not correspond to defined gridOptions.columnTypes`) → raw text, **no** format/parse/filter.
66
+ - f-sheet adds a `console.error` naming column + valid names (scans group `children` too) — **visible in `fui module simulate --render`**; AG Grid's warning isn't.
67
67
 
68
- Đặt một tên **chưa đăng ký** (`"date_time"`, `"currency"`, `"text"`, `"string"`, `"boolean"`…) thì:
68
+ Other types (currency, percent, datetime…) → declare `valueFormatter`/`valueParser`/`filter` on the colDef, never a new `type`.
69
69
 
70
- - AG Grid **bỏ qua hoàn toàn** và chỉ log `warning #36 colDef.type 'x' does not correspond to defined gridOptions.columnTypes` — cột chạy y hệt cột text thô, **không** format, **không** parse, **không** filter. Cấu hình trông như có tác dụng nhưng không có.
71
- - f-sheet phát thêm một `console.error` nêu đích danh tên cột + danh sách tên hợp lệ (quét cả colDef nằm trong `children` của column group). Dòng này **hiện lên trong `module_simulate({ renderUI: true })`** — warning gốc của AG Grid thì không.
70
+ ### Built-in cell renderers
72
71
 
73
- Cần một kiểu khác hai kiểu trên (tiền tệ, phần trăm, ngày-giờ…) thì **đừng đặt tên mới cho `type`** — khai thẳng `valueFormatter`/`valueParser`/`filter` trên colDef đó.
72
+ Use `"cellRenderer": "name"` + `cellRendererParams`; nothing to declare in `script.js`:
74
73
 
75
- ### Cell renderer dựng sẵn (`cellRenderer`)
74
+ | Renderer | Purpose | `cellRendererParams` |
75
+ | ---------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
76
+ | `fsHtmlCellRenderer` | raw HTML `value` (like f-table `t-html`) | _(none)_ |
77
+ | `fsLinkCellRenderer` | link | `urlField`/`textField` (row field for URL/label, default `value`), `icon` (mdi, optional), `target`: `'dialog'` → `openWindow({id: wid, title, url, onclose})`, else `<a>` target (default `_blank`) |
78
+ | `fsButtonCellRenderer` | small button | `label`, `icon` (mdi, optional), `color` (default `#1976d2`); click: `url` → `openWindow({id: wid, title, url, onclose})`, no `url` → `tableActionEvent({action, item: data})` |
79
+ | `fsMenuCellRenderer` | `⋮` per-row dropdown | `items`: array `{icon, text, color, action}` or `function(params)` returning it (per-row); `icon` for trigger (default `mdi-dots-vertical`). Click → `tableActionEvent({action: item.action, item: data})` |
76
80
 
77
- f-sheet đăng ký sẵn 4 renderer — dùng ngay qua `"cellRenderer": "tênRenderer"` + `cellRendererParams`, không cần khai gì thêm trong `script.js`:
81
+ `openWindow`/`tableActionEvent` are FUI globals (as in f-table `t-link`/`t-button`/`t-menu`). **Own global functions** also work for `cellRenderer`/`cellEditor`/`cellEditorParams`/`cellRendererParams`: define in `script.js`, reference by string name; auto-registered (incl. group `children`).
78
82
 
79
- | Renderer | Mục đích | `cellRendererParams` |
80
- |---|---|---|
81
- | `fsHtmlCellRenderer` | In `value` ra dạng HTML thô (giống `t-html` của f-table) | *(không cần)* |
82
- | `fsLinkCellRenderer` | Ô dạng link | `urlField`/`textField` (field khác trong dòng làm URL/nhãn, mặc định dùng `value`), `icon` (mdi, tuỳ chọn), `target` — `'dialog'` mở qua `openWindow({id: wid, title, url, onclose})`, còn lại là target của thẻ `<a>` (mặc định `_blank`) |
83
- | `fsButtonCellRenderer` | Ô dạng nút bấm nhỏ | `label`, `icon` (mdi, tuỳ chọn), `color` (mặc định `#1976d2`); bấm vào: có `url` → `openWindow({id: wid, title, url, onclose})`, không có `url` → `tableActionEvent({action, item: data})` (action đặt tên trong `action`) |
84
- | `fsMenuCellRenderer` | Nút `⋮` mở menu thả xuống theo dòng | `items` — mảng `{icon, text, color, action}` hoặc hàm `function(params)` trả về mảng đó (để tính động theo dòng); `icon` cho nút mở menu (mặc định `mdi-dots-vertical`). Bấm một item → `tableActionEvent({action: item.action, item: data})` |
83
+ ### `"type": "number"` — auto-attached (only if colDef doesn't declare it)
85
84
 
86
- `fsButtonCellRenderer`/`fsMenuCellRenderer` dùng lại `openWindow`/`tableActionEvent` — 2 hàm global gốc của FUI (giống hệt cách `t-link`/`t-button`/`t-menu` của `f-table` hoạt động), không phải cơ chế riêng của f-sheet.
85
+ | Key | Behavior |
86
+ | ---------------- | -------------------------------------------------------------------------------------------------------------------- |
87
+ | `cellStyle` | right-aligned |
88
+ | `valueFormatter` | `toLocaleString('en-US', { maximumFractionDigits: 10 })` → `1,234,567.89`; **display only**, editor shows raw number |
89
+ | `valueParser` | empty/`null`/`undefined` → real `null` (not `""`); numeric string → `Number`; invalid → `null`. Also on Excel paste |
90
+ | `filter` | `agNumberColumnFilter` |
87
91
 
88
- **Tên hàm global tự viết** cũng dùng được trực tiếp cho cả `cellRenderer`/`cellEditor` lẫn `cellEditorParams`/`cellRendererParams` — khai hàm đó trong `script.js` của module rồi tham chiếu bằng tên (string). f-sheet tự nhận diện và đăng ký (kể cả khi colDef nằm trong `children` của một column group), không cần đăng ký thủ công.
92
+ ### `"type": "date"` — auto-attached (only if colDef doesn't declare it)
89
93
 
90
- ### Cột số — `"type": "number"`
94
+ | Key | Behavior |
95
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
96
+ | `cellStyle` | centered |
97
+ | `valueFormatter` | `dd/mm/yyyy`; non-date → **raw string** shown (junk stays visible) |
98
+ | `valueParser` | accepts `dd/mm/yyyy`, `dd-mm-yyyy`, `yyyy-mm-dd`, ISO `2026-08-15T00:00:00` → stores `yyyy-mm-dd`; empty/invalid → `null`. Also on Excel paste |
99
+ | `filter` | `agDateColumnFilter` |
91
100
 
92
- Khai `"type": "number"` là đủ, f-sheet tự gắn 4 thứ (mỗi thứ chỉ gắn khi colDef **chưa** tự khai, nên muốn khác mặc định thì cứ khai đè):
101
+ **Value contract (what the SP gets):** **`yyyy-mm-dd`** or `null` — matches `f-date` and tAPI `OPENJSON ... NgayX date '$.NgayX'`. Invalid (`31/02/2026`, `15/13/2026`) → `null`, no rollover. **Copy/Excel export use the raw value** (`2026-08-15`) — intentional, lossless round-trip.
93
102
 
94
- | Tự gắn | Hành vi |
95
- |---|---|
96
- | `cellStyle` | Canh **phải** |
97
- | `valueFormatter` | Phân cách nghìn `toLocaleString('en-US', { maximumFractionDigits: 10 })` → `1,234,567.89`. Chỉ áp lúc **hiển thị** — click vào ô sửa vẫn thấy số thô |
98
- | `valueParser` | Parse khi rời ô sửa: rỗng/`null`/`undefined` → `null` thật (không phải `""`), chuỗi số → `Number`, chuỗi hỏng → `null`. Áp cho cả paste-từ-Excel |
99
- | `filter` | `agNumberColumnFilter` (lọc lớn hơn/nhỏ hơn/bằng thay vì lọc text) |
103
+ #### Date picker
100
104
 
101
- ### Cột ngày — `"type": "date"`
102
-
103
- Khai `"type": "date"` là đủ, f-sheet tự gắn 4 thứ (mỗi thứ chỉ gắn khi colDef **chưa** tự khai):
104
-
105
- | Tự gắn | Hành vi |
106
- |---|---|
107
- | `cellStyle` | Canh **giữa** |
108
- | `valueFormatter` | Hiển thị `dd/mm/yyyy`. Giá trị không phải ngày → in **chuỗi thô** (để dữ liệu rác hiện ra, không biến ô thành trống) |
109
- | `valueParser` | Nhận `dd/mm/yyyy`, `dd-mm-yyyy`, `yyyy-mm-dd` và ISO datetime `2026-08-15T00:00:00` → lưu `yyyy-mm-dd`. Rỗng → `null`. Sai → `null`. Áp cho cả paste-từ-Excel |
110
- | `filter` | `agDateColumnFilter` (lọc trước/sau/bằng ngày) |
111
-
112
- **Hợp đồng giá trị — đây là thứ quyết định SP nhận được gì:** ô ngày luôn giữ chuỗi **`yyyy-mm-dd`** (hoặc `null`). Khớp thẳng với `f-date` (cũng emit `yyyy-mm-dd`) và với `OPENJSON ... NgayX date '$.NgayX'` phía tAPI — không phải convert gì thêm.
113
-
114
- - **Ngày không hợp lệ về `null`, ô thành rỗng** — giống hệt cột số. `31/02/2026` và `15/13/2026` bị từ chối (không âm thầm cuộn sang `03/03/2026` / `15/01/2027`).
115
- - **Copy và Xuất Excel lấy giá trị thô** ⇒ ra `2026-08-15`, không phải `15/08/2026`. Cố ý: dán ngược vào lưới thì parse lại được, round-trip không mất mát.
116
-
117
- #### Muốn có date picker
118
-
119
- `type: "date"` lo hiển thị/parse/lọc, **không tự gắn editor** — sửa ô vẫn là gõ text. Cần picker thì khai thêm:
105
+ `type: "date"` adds **no editor** (text typing). For a picker:
120
106
 
121
107
  ```json
122
108
  { "field": "NgayThi", "headerName": "Ngày thi", "type": "date", "cellEditor": "agDateStringCellEditor" }
123
109
  ```
124
110
 
125
- `agDateStringCellEditor` là editor **có sẵn của AG Grid**, không cần đăng ký gì. Nhưng nó đọc **giá trị thô** chứ không qua `valueFormatter`, nên ô đang giữ ISO datetime (`2026-08-15T00:00:00`) sẽ mở ra **rỗng** cho tới khi được sửa một lần. Cho SP trả `CONVERT(date, NgayThi)` là hết — cũng là lý do f-sheet không gắn editor này mặc định (gắn ngầm thì bấm Enter là ghi `null` đè lên ngày đang có).
111
+ AG Grid built-in. Reads the **raw value**: ISO datetime (`2026-08-15T00:00:00`) opens **empty** until edited once → SP returns `CONVERT(date, NgayThi)`. Not attached by default because Enter would write `null` over the date.
126
112
 
127
- > **Không có editor nào tên `fsheetDate`.** Tên đó **từng tồn tại** trong `componentTable*.js` rồi `fsheet.js` và **đã bị gỡ**. Module cũ còn khai `"cellEditor": "fsheetDate"` thì đổi sang `"type": "date"` (thêm `agDateStringCellEditor` nếu cần picker). Luật nền cần nhớ: `cellEditor`/`cellRenderer` dạng string chỉ resolve được khi `window[tên]` là một function — tên không tồn tại thì f-sheet **im lặng** bỏ qua và AG Grid rơi về editor text, không lỗi, không cảnh báo.
113
+ > **`fsheetDate` was removed.** Old `"cellEditor": "fsheetDate"` → `"type": "date"` (+ `agDateStringCellEditor`). String `cellEditor`/`cellRenderer` resolves only if `window[name]` is a function; unknown names **silently** fall back to the text editor.
128
114
 
129
- ### Chiều cao — `:height`
115
+ ### `:height`
130
116
 
131
- | Giá trị | Hành vi |
132
- |---|---|
133
- | *(bỏ trống)* | **Tự giãn theo số dòng** — AG Grid `domLayout: 'autoHeight'`, lưới cao bằng đúng nội dung, không có thanh cuộn riêng |
134
- | `<= 0` | **Fill tới đáy màn hình, chừa `abs(height)` px** |
135
- | `> 0` | px cố định (vd `":height": 300`) |
117
+ | Value | Behavior |
118
+ | --------- | ------------------------------------------------------------- |
119
+ | _(empty)_ | **auto-grow** — `domLayout: 'autoHeight'`, no inner scrollbar |
120
+ | `<= 0` | **fill to screen bottom, leaving `abs(height)` px** |
121
+ | `> 0` | fixed px (e.g. `":height": 300`) |
136
122
 
137
- Chế độ `<=0` **đo `getBoundingClientRect().top` thật** của phần tử lưới sau khi layout xong rồi tính `window.innerHeight - top - abs(height)` (sàn tối thiểu 200px), và đăng ký `window.resize` để tự cập nhật. Nghĩa là **không cần biết** header/toolbar/breadcrumb phía trên cao bao nhiêu — chỉ khai khoảng hở muốn chừa ở đáy.
123
+ `<=0` = `window.innerHeight - getBoundingClientRect().top - abs(height)` (min 200px), measured after layout, updated on `window.resize` — only the bottom gap matters.
138
124
 
139
- - **Bảng chính của module**: khai `":height": -16` — cuộn data chỉ cuộn trong lưới, trang không cuộn (lint D9). Thiếu `:height` sẽ bị D9 cảnh báo.
140
- - **Trong dialog**: dùng số **dương** (px cố định), vì lưới không nằm sát đáy viewport.
141
- - **Bảng ngắn/lookup ít dòng**: bỏ trống `:height` để tự giãn — hợp lệ, bỏ qua D9.
142
- - ⚠ Chế độ tự giãn **render toàn bộ dòng** (AG Grid không ảo hoá được khi không có chiều cao cố định). Vài trăm dòng trở lên thì phải khai `:height` để lấy lại virtual scrolling.
143
- - ⚠ Số âm **không** có nghĩa "viewport trừ đi N". Module cũ khai `":height": -220` phải sửa thành `-16`, nếu không lưới hụt mất ~200px.
125
+ - Main table: `":height": -16` (scrolls inside grid; lint D9 warns if `:height` missing).
126
+ - Dialog: **positive** px.
127
+ - Short/lookup tables: omit (auto-grow, exempt from D9). ⚠ Auto-grow renders every row, no virtualization — hundreds of rows → set `:height`.
128
+ - ⚠ Negative ≠ "viewport minus N": old `-220` → `-16`, else ~200px short.
144
129
 
145
130
  ---
146
131
 
147
- ## Wiring API — hợp đồng response
132
+ ## API wiring — response contract
148
133
 
149
- `update-api` và `delete-api` được gọi bằng `ajaxCALL(api, { ...saveExtra, ...row })`. **SP nên trả về một dòng** dạng:
134
+ Called as `ajaxCALL(api, { ...saveExtra, ...row })`. **SP returns one row:**
150
135
 
151
136
  ```
152
137
  SELECT Success = 1, Message = N'Đã lưu' -- hoặc Success = 0 khi lỗi nghiệp vụ
153
138
  ```
154
139
 
155
- f-sheet đọc `res.data[0]` → hiện snackbar `Message`; `Success` quyết định đánh dấu thành công. Xem quy ước SP: [tapi-reference.md](tapi-reference.md).
140
+ Reads `res.data[0]`: `Message` → snackbar, `Success` → marks success. See [tapi-reference.md](tapi-reference.md).
156
141
 
157
- - **Dòng mới**: nhận khóa **âm** tạm (−1, −2…) và tô nền **xanh nhạt**; khi lưu, khóa âm được đặt `0` trong payload → SP hiểu là INSERT.
158
- - **Ô đã sửa**: tô nền **vàng nhạt**; "N dòng thay đổi" hiển thị trên toolbar.
159
- - Lưu chạy **từng dòng song song**; xong hết & không lỗi → làm mới baseline, xoá đánh dấu, emit `saved`.
160
- - Xoá nhiều dòng đã tồn tại (đã khai `delete-api`) → hỏi `confirm` trước rồi mới gọi API cho từng dòng; **chưa khai `delete-api`** thì xoá cục bộ ngay, không hỏi. Dòng mới (khóa âm) luôn xoá cục bộ ngay, không hỏi, không gọi API.
142
+ - **New rows**: temp **negative** key (−1, −2…), **light green**; on save key → `0` in payload → SP INSERTs.
143
+ - **Edited cells**: **light yellow**; toolbar shows "N rows changed".
144
+ - Save runs **per row, in parallel**; all OK → baseline refreshed, marks cleared, `saved` emitted.
145
+ - Delete existing rows: with `delete-api` → `confirm`, then API per row; without → local, no prompt. New rows → always local, no prompt, no API.
161
146
 
162
147
  ---
163
148
 
164
- ## Tính năng có sẵn (không cần tự code)
149
+ ## Built-in (don't code)
165
150
 
166
- - **Sửa inline** + theo dõi thay đổi (vàng nhạt = ô đã đổi, xanh nhạt = dòng mới).
167
- - **Chọn nhiều ô kiểu Excel**: kéo chuột hoặc Shift+click để chọn một khối ô liên tục.
168
- - **Fill handle**: kéo núm vuông ở góc dưới-phải vùng đang chọn để điền lặp lại khối nguồn sang các ô lân cận (theo chiều kéo dài hơn — ngang hoặc dọc).
169
- - **Copy/Cut/Paste dạng TSV** (tương thích Excel/Google Sheets): phím tắt Ctrl+C/Ctrl+X/Ctrl+V khi đang focus trong lưới, hoặc menu chuột phải (Cut/Copy/Copy with Headers/Paste).
170
- - **Phím Escape** bỏ vùng đang chọn.
171
- - Toolbar: **Xuất Excel** (luôn có, dùng `jsonToExcel`) · **Thêm** (`allow-add`) · **Lưu** (hiện khi có dòng đổi, `disabled` nếu chưa khai `update-api`) · **Huỷ thay đổi** (hiện khi có dòng đổi) · **Xoá đã chọn** (`allow-delete`, multi-select, chỉ hiện khi có dòng đang chọn).
151
+ - Inline edit + change tracking.
152
+ - Range select: drag or Shift+click.
153
+ - Fill handle: drag bottom-right square to repeat the source block (along the longer drag direction).
154
+ - TSV Copy/Cut/Paste (Excel/Google Sheets): Ctrl+C/X/V in grid, or right-click (Cut/Copy/Copy with Headers/Paste).
155
+ - Escape clears selection.
156
+ - Toolbar: **Export Excel** (always, `jsonToExcel`) · **Add** (`allow-add`) · **Save** (when changes; `disabled` without `update-api`) · **Discard changes** (when changes) · **Delete selected** (`allow-delete`, multi-select, only with selection).
172
157
 
173
- ## Events & method (`$refs`)
158
+ ## Events & methods (`$refs`)
174
159
 
175
- - Emit: `change(data, field, value)` (mỗi lần một ô đổi giá trị) · `save(rows)` (bấm Lưu, trước khi gọi API) · `saved` (lưu thành công toàn bộ).
176
- - Method: `getChangedRows()` · `addRow()` · `deleteRow(row)` · `deleteSelected()` · `discardAll()` · `exportExcel()`.
160
+ - Emit: `change(data, field, value)` (each cell change) · `save(rows)` (Save clicked, before API) · `saved` (all saved).
161
+ - Methods: `getChangedRows()` · `addRow()` · `deleteRow(row)` · `deleteSelected()` · `discardAll()` · `exportExcel()`.
177
162
 
178
163
  ---
179
164
 
180
- ## Ví dụ trong module.json (controls)
165
+ ## module.json example
181
166
 
182
167
  ```json
183
168
  {
@@ -188,11 +173,11 @@ f-sheet đọc `res.data[0]` → hiện snackbar `Message`; `Success` quyết đ
188
173
  ":items": "dsDiem",
189
174
  "row-key": "DiemID",
190
175
  ":columns": [
191
- { "field": "MaSV", "headerName": "Mã SV", "editable": false, "width": 120, "pinned": "left" },
176
+ { "field": "MaSV", "headerName": "Mã SV", "editable": false, "width": 120, "pinned": "left" },
192
177
  { "field": "HoTen", "headerName": "Họ tên", "editable": false },
193
- { "field": "Diem", "headerName": "Điểm", "type": "number" },
194
- { "field": "NgayThi","headerName": "Ngày thi", "type": "date" },
195
- { "field": "GhiChu","headerName": "Ghi chú" }
178
+ { "field": "Diem", "headerName": "Điểm", "type": "number" },
179
+ { "field": "NgayThi", "headerName": "Ngày thi", "type": "date" },
180
+ { "field": "GhiChu", "headerName": "Ghi chú" }
196
181
  ],
197
182
  ":allow-add": true,
198
183
  ":allow-delete": true,
@@ -205,14 +190,14 @@ f-sheet đọc `res.data[0]` → hiện snackbar `Message`; `Success` quyết đ
205
190
  }
206
191
  ```
207
192
 
208
- - `dsDiem` được nạp trước bằng một action trong `data[]` (`API`/`OUT`).
209
- - `Diem_Save`/`Diem_Delete` là SP tAPI trả `{ Success, Message }` mỗi dòng.
210
- - Kết hợp mở cửa sổ chi tiết: nút/menu trong toolbar hoặc cột gọi `openWindow` — xem [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md) §5.
193
+ - `dsDiem` loaded first by a `data[]` action (`API`/`OUT`).
194
+ - `Diem_Save`/`Diem_Delete`: tAPI SPs returning `{ Success, Message }` per row.
195
+ - Detail window from toolbar/column button/menu → `openWindow`: [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md) §5.
211
196
 
212
197
  ## Checklist
213
198
 
214
- 1. Đã `file_import_new` đủ **2 file AG Grid**, đúng thứ tự (`ag-grid-community.min.js` trước, `fsheet.js` sau).
215
- 2. `row-key` trỏ đúng khóa chính; SP `update-api`/`delete-api` trả `{ Success, Message }`.
216
- 3. Cần tuỳ biến cột → dùng colDef AG Grid; ô dạng link/nút/menu → 4 cell renderer dựng sẵn (`fsHtmlCellRenderer`/`fsLinkCellRenderer`/`fsButtonCellRenderer`/`fsMenuCellRenderer`); số → `type: "number"`, ngày → `type: "date"` (không tự khai `cellStyle`/`valueFormatter`/`valueParser` tay nữa). `type` **chỉ** nhận đúng 2 tên đó — kiểu khác thì khai thẳng `valueFormatter`/`valueParser`, đừng đặt tên `type` mới.
217
- 4. `:height` — bảng chính khai `-16`; trong dialog dùng số **dương**; bỏ trống = tự giãn (chỉ hợp cho bảng ngắn). Số âm = khoảng chừa ở đáy, **không phải** "viewport trừ N".
218
- 5. Đừng tự dựng lại grid/CSS — mọi cấu hình lưới đi qua `columns` (AG Grid); chọn vùng/fill/copy-paste đã có sẵn, không tự viết thêm.
199
+ 1. Both **AG Grid files** imported via `fui import new`, in order (`ag-grid-community.min.js` then `fsheet.js`).
200
+ 2. `row-key` = real PK; `update-api`/`delete-api` SPs return `{ Success, Message }`.
201
+ 3. AG Grid colDef; link/button/menu → 4 built-in renderers; `type: "number"`/`"date"` only (no manual `cellStyle`/`valueFormatter`/`valueParser`); other types → `valueFormatter`/`valueParser`.
202
+ 4. `:height`: main `-16`, dialog positive, empty only for short tables.
203
+ 5. Don't rebuild grid/CSS — config via `columns`; range select/fill/copy-paste are built in.