@fui-org/fui-cli 0.2.0 → 0.4.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 (212) hide show
  1. package/README.md +20 -2
  2. package/dist/fui.js +347 -44
  3. package/package.json +15 -3
  4. package/skills/fui/SKILL.md +149 -66
  5. package/skills/fui-skill/README.md +112 -0
  6. package/skills/fui-skill/SKILL.md +269 -0
  7. package/skills/fui-skill/assets/projectdefaultstyle.css +518 -0
  8. package/skills/fui-skill/design-md/airbnb/DESIGN.md +545 -0
  9. package/skills/fui-skill/design-md/airbnb/README.md +5 -0
  10. package/skills/fui-skill/design-md/airtable/DESIGN.md +554 -0
  11. package/skills/fui-skill/design-md/airtable/README.md +5 -0
  12. package/skills/fui-skill/design-md/apple/DESIGN.md +562 -0
  13. package/skills/fui-skill/design-md/apple/README.md +5 -0
  14. package/skills/fui-skill/design-md/asu/DESIGN.md +179 -0
  15. package/skills/fui-skill/design-md/asu/README.md +107 -0
  16. package/skills/fui-skill/design-md/binance/DESIGN.md +634 -0
  17. package/skills/fui-skill/design-md/binance/README.md +5 -0
  18. package/skills/fui-skill/design-md/bmw/DESIGN.md +544 -0
  19. package/skills/fui-skill/design-md/bmw/README.md +5 -0
  20. package/skills/fui-skill/design-md/bmw-m/DESIGN.md +503 -0
  21. package/skills/fui-skill/design-md/bmw-m/README.md +5 -0
  22. package/skills/fui-skill/design-md/bugatti/DESIGN.md +454 -0
  23. package/skills/fui-skill/design-md/bugatti/README.md +5 -0
  24. package/skills/fui-skill/design-md/cal/DESIGN.md +542 -0
  25. package/skills/fui-skill/design-md/cal/README.md +5 -0
  26. package/skills/fui-skill/design-md/claude/DESIGN.md +589 -0
  27. package/skills/fui-skill/design-md/claude/README.md +5 -0
  28. package/skills/fui-skill/design-md/clay/DESIGN.md +541 -0
  29. package/skills/fui-skill/design-md/clay/README.md +5 -0
  30. package/skills/fui-skill/design-md/clickhouse/DESIGN.md +544 -0
  31. package/skills/fui-skill/design-md/clickhouse/README.md +5 -0
  32. package/skills/fui-skill/design-md/cohere/DESIGN.md +451 -0
  33. package/skills/fui-skill/design-md/cohere/README.md +5 -0
  34. package/skills/fui-skill/design-md/coinbase/DESIGN.md +570 -0
  35. package/skills/fui-skill/design-md/coinbase/README.md +5 -0
  36. package/skills/fui-skill/design-md/composio/DESIGN.md +506 -0
  37. package/skills/fui-skill/design-md/composio/README.md +5 -0
  38. package/skills/fui-skill/design-md/cursor/DESIGN.md +537 -0
  39. package/skills/fui-skill/design-md/cursor/README.md +5 -0
  40. package/skills/fui-skill/design-md/elevenlabs/DESIGN.md +504 -0
  41. package/skills/fui-skill/design-md/elevenlabs/README.md +5 -0
  42. package/skills/fui-skill/design-md/expo/DESIGN.md +526 -0
  43. package/skills/fui-skill/design-md/expo/README.md +5 -0
  44. package/skills/fui-skill/design-md/ferrari/DESIGN.md +531 -0
  45. package/skills/fui-skill/design-md/ferrari/README.md +5 -0
  46. package/skills/fui-skill/design-md/figma/DESIGN.md +578 -0
  47. package/skills/fui-skill/design-md/figma/README.md +5 -0
  48. package/skills/fui-skill/design-md/framer/DESIGN.md +544 -0
  49. package/skills/fui-skill/design-md/framer/README.md +5 -0
  50. package/skills/fui-skill/design-md/fui/DESIGN.md +532 -0
  51. package/skills/fui-skill/design-md/hashicorp/DESIGN.md +575 -0
  52. package/skills/fui-skill/design-md/hashicorp/README.md +5 -0
  53. package/skills/fui-skill/design-md/ibm/DESIGN.md +550 -0
  54. package/skills/fui-skill/design-md/ibm/README.md +5 -0
  55. package/skills/fui-skill/design-md/intercom/DESIGN.md +546 -0
  56. package/skills/fui-skill/design-md/intercom/README.md +5 -0
  57. package/skills/fui-skill/design-md/kraken/DESIGN.md +125 -0
  58. package/skills/fui-skill/design-md/kraken/README.md +5 -0
  59. package/skills/fui-skill/design-md/lamborghini/DESIGN.md +288 -0
  60. package/skills/fui-skill/design-md/lamborghini/README.md +5 -0
  61. package/skills/fui-skill/design-md/linear.app/DESIGN.md +548 -0
  62. package/skills/fui-skill/design-md/linear.app/README.md +5 -0
  63. package/skills/fui-skill/design-md/lovable/DESIGN.md +298 -0
  64. package/skills/fui-skill/design-md/lovable/README.md +5 -0
  65. package/skills/fui-skill/design-md/mastercard/DESIGN.md +365 -0
  66. package/skills/fui-skill/design-md/mastercard/README.md +5 -0
  67. package/skills/fui-skill/design-md/meta/DESIGN.md +683 -0
  68. package/skills/fui-skill/design-md/meta/README.md +5 -0
  69. package/skills/fui-skill/design-md/minimax/DESIGN.md +746 -0
  70. package/skills/fui-skill/design-md/minimax/README.md +5 -0
  71. package/skills/fui-skill/design-md/mintlify/DESIGN.md +852 -0
  72. package/skills/fui-skill/design-md/mintlify/README.md +5 -0
  73. package/skills/fui-skill/design-md/miro/DESIGN.md +825 -0
  74. package/skills/fui-skill/design-md/miro/README.md +5 -0
  75. package/skills/fui-skill/design-md/mistral.ai/DESIGN.md +773 -0
  76. package/skills/fui-skill/design-md/mistral.ai/README.md +5 -0
  77. package/skills/fui-skill/design-md/mongodb/DESIGN.md +767 -0
  78. package/skills/fui-skill/design-md/mongodb/README.md +5 -0
  79. package/skills/fui-skill/design-md/nike/DESIGN.md +575 -0
  80. package/skills/fui-skill/design-md/nike/README.md +5 -0
  81. package/skills/fui-skill/design-md/notion/DESIGN.md +821 -0
  82. package/skills/fui-skill/design-md/notion/README.md +5 -0
  83. package/skills/fui-skill/design-md/nvidia/DESIGN.md +640 -0
  84. package/skills/fui-skill/design-md/nvidia/README.md +5 -0
  85. package/skills/fui-skill/design-md/ollama/DESIGN.md +539 -0
  86. package/skills/fui-skill/design-md/ollama/README.md +5 -0
  87. package/skills/fui-skill/design-md/opencode.ai/DESIGN.md +521 -0
  88. package/skills/fui-skill/design-md/opencode.ai/README.md +5 -0
  89. package/skills/fui-skill/design-md/pinterest/DESIGN.md +597 -0
  90. package/skills/fui-skill/design-md/pinterest/README.md +5 -0
  91. package/skills/fui-skill/design-md/playstation/DESIGN.md +661 -0
  92. package/skills/fui-skill/design-md/playstation/README.md +5 -0
  93. package/skills/fui-skill/design-md/posthog/DESIGN.md +690 -0
  94. package/skills/fui-skill/design-md/posthog/README.md +5 -0
  95. package/skills/fui-skill/design-md/raycast/DESIGN.md +669 -0
  96. package/skills/fui-skill/design-md/raycast/README.md +5 -0
  97. package/skills/fui-skill/design-md/renault/DESIGN.md +589 -0
  98. package/skills/fui-skill/design-md/renault/README.md +5 -0
  99. package/skills/fui-skill/design-md/replicate/DESIGN.md +616 -0
  100. package/skills/fui-skill/design-md/replicate/README.md +5 -0
  101. package/skills/fui-skill/design-md/resend/DESIGN.md +585 -0
  102. package/skills/fui-skill/design-md/resend/README.md +5 -0
  103. package/skills/fui-skill/design-md/revolut/DESIGN.md +636 -0
  104. package/skills/fui-skill/design-md/revolut/README.md +5 -0
  105. package/skills/fui-skill/design-md/runwayml/DESIGN.md +244 -0
  106. package/skills/fui-skill/design-md/runwayml/README.md +5 -0
  107. package/skills/fui-skill/design-md/sanity/DESIGN.md +357 -0
  108. package/skills/fui-skill/design-md/sanity/README.md +5 -0
  109. package/skills/fui-skill/design-md/sentry/DESIGN.md +551 -0
  110. package/skills/fui-skill/design-md/sentry/README.md +5 -0
  111. package/skills/fui-skill/design-md/shopify/DESIGN.md +516 -0
  112. package/skills/fui-skill/design-md/shopify/README.md +5 -0
  113. package/skills/fui-skill/design-md/slack/DESIGN.md +482 -0
  114. package/skills/fui-skill/design-md/spacex/DESIGN.md +363 -0
  115. package/skills/fui-skill/design-md/spacex/README.md +5 -0
  116. package/skills/fui-skill/design-md/spotify/DESIGN.md +246 -0
  117. package/skills/fui-skill/design-md/spotify/README.md +5 -0
  118. package/skills/fui-skill/design-md/starbucks/DESIGN.md +580 -0
  119. package/skills/fui-skill/design-md/starbucks/README.md +5 -0
  120. package/skills/fui-skill/design-md/stripe/DESIGN.md +487 -0
  121. package/skills/fui-skill/design-md/stripe/README.md +5 -0
  122. package/skills/fui-skill/design-md/supabase/DESIGN.md +462 -0
  123. package/skills/fui-skill/design-md/supabase/README.md +5 -0
  124. package/skills/fui-skill/design-md/superhuman/DESIGN.md +448 -0
  125. package/skills/fui-skill/design-md/superhuman/README.md +5 -0
  126. package/skills/fui-skill/design-md/tesla/DESIGN.md +286 -0
  127. package/skills/fui-skill/design-md/tesla/README.md +5 -0
  128. package/skills/fui-skill/design-md/theverge/DESIGN.md +339 -0
  129. package/skills/fui-skill/design-md/theverge/README.md +5 -0
  130. package/skills/fui-skill/design-md/together.ai/DESIGN.md +633 -0
  131. package/skills/fui-skill/design-md/together.ai/README.md +5 -0
  132. package/skills/fui-skill/design-md/uber/DESIGN.md +636 -0
  133. package/skills/fui-skill/design-md/uber/README.md +5 -0
  134. package/skills/fui-skill/design-md/vercel/DESIGN.md +736 -0
  135. package/skills/fui-skill/design-md/vercel/README.md +5 -0
  136. package/skills/fui-skill/design-md/vodafone/DESIGN.md +538 -0
  137. package/skills/fui-skill/design-md/vodafone/README.md +5 -0
  138. package/skills/fui-skill/design-md/voltagent/DESIGN.md +521 -0
  139. package/skills/fui-skill/design-md/voltagent/README.md +5 -0
  140. package/skills/fui-skill/design-md/warp/DESIGN.md +526 -0
  141. package/skills/fui-skill/design-md/warp/README.md +5 -0
  142. package/skills/fui-skill/design-md/webflow/DESIGN.md +588 -0
  143. package/skills/fui-skill/design-md/webflow/README.md +5 -0
  144. package/skills/fui-skill/design-md/wired/DESIGN.md +497 -0
  145. package/skills/fui-skill/design-md/wired/README.md +5 -0
  146. package/skills/fui-skill/design-md/wise/DESIGN.md +544 -0
  147. package/skills/fui-skill/design-md/wise/README.md +5 -0
  148. package/skills/fui-skill/design-md/x.ai/DESIGN.md +465 -0
  149. package/skills/fui-skill/design-md/x.ai/README.md +5 -0
  150. package/skills/fui-skill/design-md/zapier/DESIGN.md +537 -0
  151. package/skills/fui-skill/design-md/zapier/README.md +5 -0
  152. package/skills/fui-skill/examples/component.vue +162 -0
  153. package/skills/fui-skill/examples/f-table-patterns.json +331 -0
  154. package/skills/fui-skill/examples/module-patterns.json +973 -0
  155. package/skills/fui-skill/examples/project-patterns.json +222 -0
  156. package/skills/fui-skill/metadata.json +75 -0
  157. package/skills/fui-skill/references/INDEX.md +144 -0
  158. package/skills/fui-skill/references/advanced-techniques.md +160 -0
  159. package/skills/fui-skill/references/coding-standards.md +112 -0
  160. package/skills/fui-skill/references/component-design.md +455 -0
  161. package/skills/fui-skill/references/component-quickref.md +77 -0
  162. package/skills/fui-skill/references/component-table.md +276 -0
  163. package/skills/fui-skill/references/components-dialog.md +192 -0
  164. package/skills/fui-skill/references/components-display.md +147 -0
  165. package/skills/fui-skill/references/components-echart.md +391 -0
  166. package/skills/fui-skill/references/components-input.md +359 -0
  167. package/skills/fui-skill/references/controls-patterns.md +847 -0
  168. package/skills/fui-skill/references/controls-styling-vocabulary.md +140 -0
  169. package/skills/fui-skill/references/db-table-design.md +77 -0
  170. package/skills/fui-skill/references/db-workflow.md +504 -0
  171. package/skills/fui-skill/references/default-function.md +415 -0
  172. package/skills/fui-skill/references/design-modes.md +85 -0
  173. package/skills/fui-skill/references/echart-templates.md +481 -0
  174. package/skills/fui-skill/references/fastproject.md +97 -0
  175. package/skills/fui-skill/references/fsheet.md +218 -0
  176. package/skills/fui-skill/references/fullstack-workflow.md +351 -0
  177. package/skills/fui-skill/references/module-data-patterns.md +126 -0
  178. package/skills/fui-skill/references/module-json-anatomy.md +137 -0
  179. package/skills/fui-skill/references/module-structure.md +260 -0
  180. package/skills/fui-skill/references/new-session.md +108 -0
  181. package/skills/fui-skill/references/pdfmake.md +60 -0
  182. package/skills/fui-skill/references/permission-system.md +169 -0
  183. package/skills/fui-skill/references/platform-architecture.md +294 -0
  184. package/skills/fui-skill/references/project-config.md +335 -0
  185. package/skills/fui-skill/references/project-provisioning.md +383 -0
  186. package/skills/fui-skill/references/script-map.md +296 -0
  187. package/skills/fui-skill/references/sql-clr-functions.md +224 -0
  188. package/skills/fui-skill/references/system-design.md +116 -0
  189. package/skills/fui-skill/references/tapi-file-api.md +191 -0
  190. package/skills/fui-skill/references/tapi-permission-patterns.md +158 -0
  191. package/skills/fui-skill/references/tapi-reference.md +549 -0
  192. package/skills/fui-skill/references/tools-registry.md +460 -0
  193. package/skills/fui-skill/references/ui-crosswindow-patterns.md +317 -0
  194. package/skills/fui-skill/references/ui-dialog-patterns.md +229 -0
  195. package/skills/fui-skill/references/ui-layout-patterns.md +176 -0
  196. package/skills/fui-skill/references/ui-patterns.md +315 -0
  197. package/skills/fui-skill/references/ui-screenshot-review.md +94 -0
  198. package/skills/fui-skill/references/ui-table-cell-patterns.md +316 -0
  199. package/skills/fui-skill/references/ui-templates.md +29 -0
  200. package/skills/fui-skill/references/verification.md +246 -0
  201. package/skills/fui-skill/references/watcher-patterns.md +196 -0
  202. package/skills/fui-skill/references/websocket-realtime.md +254 -0
  203. package/skills/fui-skill/scripts/component-3.0.js +2549 -0
  204. package/skills/fui-skill/scripts/component.js +3142 -0
  205. package/skills/fui-skill/scripts/componentTable-3.0.js +909 -0
  206. package/skills/fui-skill/scripts/componentTable.js +769 -0
  207. package/skills/fui-skill/scripts/defaultfunction-3.0.js +781 -0
  208. package/skills/fui-skill/scripts/defaultfunction.js +966 -0
  209. package/skills/fui-skill/scripts/fastproject-3.0.js +870 -0
  210. package/skills/fui-skill/scripts/fastproject.js +828 -0
  211. package/skills/fui-skill/scripts/fechart.js +890 -0
  212. package/skills/fui-skill/scripts/fsheet.js +1330 -0
@@ -0,0 +1,112 @@
1
+ # FUI Coding Standards
2
+
3
+ > File này sở hữu: **chuẩn code chung: đặt tên, quy ước `<style scoped>` trong `.vue` (scope thủ công), cấm backtick trong `<template>`, reusable API**. Quy tắc thiết kế component chi tiết xem [component-design.md](component-design.md).
4
+
5
+ ## 1. Naming Conventions
6
+
7
+ ### Components (`components/`)
8
+ - **Prefix**: Use `uc-` (User Component) for custom module components to distinguish from standard FUI components (`f-`, `t-`).
9
+ - **Format**: Kebab-case (e.g., `uc-trangthai-sukien.vue`, `uc-user-profile.vue`).
10
+ - **Props**: Use camelCase in script, kebab-case in templates (e.g., `userProfile` -> `:user-profile`).
11
+ - **Prefer reusable APIs**: Default to generic props, emits, and slots so the component can be reused across modules or screens.
12
+ - **Avoid overfitting names**: Prefer neutral contracts like `items`, `value`, `label`, `loading`, `readonly`, `disabled`, `options`, `config` unless the business domain truly requires a specific prop name.
13
+ - **Push orchestration upward**: Keep API calls, route changes, and page-specific coordination in `module.json` or the parent when possible. Let the component focus on presentation and local interaction.
14
+ - **Emit upward**: Prefer `$emit(...)` for `input`, `change`, `select`, `submit`, `remove`, or explicit action events rather than mutating parent-owned state.
15
+
16
+ ### CSS Classes
17
+ - **`<style scoped>` in `.vue` is allowed** (saved server-side as `ComCSS`), but `scoped` has **no real scoping effect** — FUI only concatenates `ComCSS` from every component in the module into one global stylesheet (`ComponentCSSBuild()`), no AST transform, no `data-v-xxxx` attribute injection. **Every selector must be manually scoped**: give the component's root template element a unique class matching the component name (kebab-case, e.g. `uc-invoice-list`), then write every rule as a descendant selector starting from that class (`.uc-invoice-list__title`, never a bare `.title`). See [component-design.md](component-design.md) for a full example.
18
+ - **Style Placement**: Component-local CSS → `<style scoped>` in the `.vue` file (with manual scoping above). Module-wide CSS → `style.css` (publish via `module_publish_css`). `header.html` is still valid for CSS but no longer the only option.
19
+ - **Vuetify Utilities**: Prioritize Vuetify helper classes (e.g., `ma-2`, `pa-0`, `d-flex`, `primary--text`).
20
+ - **Custom Classes**: Use meaningful prefixed names (e.g., `ep-hero`, `ep-field-grid`). Avoid generic names like `.box` or `.red`.
21
+ - **State Classes**: Use descriptive names for state (e.g., `.is-active`, `.has-error`).
22
+
23
+ ### Action Keys (`module.json`)
24
+ - **API Actions**: Predix with `api` (e.g., `apiGetDSSuKien`, `apiUpdateUser`).
25
+ - **Event Handlers**: Prefix with `handle` (e.g., `handleOpenReport`, `handleSubmit`).
26
+ - **Dialog Actions**: Prefix with verb (e.g., `openUploadDialog`, `closeSettings`).
27
+
28
+ ## 2. Menu Configuration (`set.menu`)
29
+
30
+ Define the application menu in the `set` object of `module.json`.
31
+
32
+ ```json
33
+ "menu": [
34
+ {
35
+ "name": "Main Group",
36
+ "icon": "mdi-home", // Material Design Icons
37
+ "url": "/dashboard", // Route
38
+ "right": { // Permission check
39
+ "SystemRight": [1, 2] // Array of allowed Right IDs
40
+ }
41
+ },
42
+ {
43
+ "name": "Management",
44
+ "icon": "mdi-cog",
45
+ "submenu": [ // Nested menu items
46
+ {
47
+ "name": "Users",
48
+ "url": "/users"
49
+ },
50
+ {
51
+ "name": "Settings",
52
+ "url": "/settings"
53
+ }
54
+ ]
55
+ }
56
+ ]
57
+ ```
58
+
59
+ ## 3. Project Structure
60
+
61
+ - **Canonical module structure**: Follow [module-structure.md](module-structure.md) as the default module layout in both workspace and chat contexts. In workspace-aware environments, apply it to real local files. In chat contexts, use it to virtualize the same module structure in the response.
62
+ - **Required core files**: Keep `_info.json` and `module.json` at module root.
63
+ - **`components/`**: Only place `.vue` files here. Do not sub-folder unless strictly necessary (FUI auto-scans this root).
64
+ - **`components/_components.json`**: Maintain component registry when using `uc-*` components.
65
+ - **`header.html`**: Still valid for CSS, but component CSS should prefer `<style scoped>` in the `.vue` (manually scoped) and module-wide CSS should prefer `style.css`.
66
+ - **`module.json`**: Keep this file clean. Move large static lists to the database or separate JSON files if supported.
67
+
68
+ ## 4. Best Practices
69
+
70
+ - **Data Binding**: Avoid complex logic in JSON attributes. Use computed properties in components or simpler `vueData` structures.
71
+ - **Event Handling**: Use `CALL(vueData.actionName)` for all complex interactions. Avoid inline JS like `vueData.count++` for anything beyond simple toggles.
72
+ - **Mobile Responsiveness**: Always configure `configForm.xs` and `configForm.md` for responsive form widths.
73
+
74
+ ## 5. Component Registration (`_components.json`)
75
+
76
+ Components are registered using an **upsert pattern**:
77
+
78
+ - **New component** (before publish): Only `comName` is required. Do NOT manually assign `comID`.
79
+ ```json
80
+ [
81
+ { "comName": "hr-employee-profile" }
82
+ ]
83
+ ```
84
+ - **After publish & sync**: The server auto-assigns `comID`. The file gets updated on sync:
85
+ ```json
86
+ [
87
+ { "comID": 7813, "comName": "hr-employee-profile" }
88
+ ]
89
+ ```
90
+
91
+ > **Rule**: Never manually create or modify `comID`. It is server-generated.
92
+
93
+ ## 6. Vue Component Design
94
+
95
+ Xem toàn bộ quy tắc thiết kế `uc-*.vue` component tại [component-design.md](component-design.md), bao gồm:
96
+ - Không dùng `components: {}` để đăng ký
97
+ - `<style scoped>` trong file `.vue` phải tự scope thủ công (xem mục CSS Classes ở trên)
98
+ - Không dùng backtick trong `<template>`
99
+ - Không dùng `v-dialog` — dùng cấu trúc `v-overlay` thay thế
100
+ - Cấu trúc bắt buộc cho dialog component
101
+
102
+ ## 7. Complex Module Architecture
103
+
104
+ When a module's `module.json` controls exceed ~200 lines:
105
+
106
+ - **Extract to Vue component**: Move the UI into a `.vue` component. Keep `module.json` lean (data/API only + a single component call in `controls`).
107
+ - **Props binding**: Pass all data from `module.json` via props. Use kebab-case for prop names in templates (`:nhan-vien="nhanVien"`).
108
+ - **Design for reuse first**: Before naming props or methods, check whether the component can be expressed as a generic list, card, dialog body, filter panel, summary block, or form section reused by other modules.
109
+ - **Configurable states**: Expose loading, empty, disabled, and readonly behavior through props or slots instead of hardcoding one workflow.
110
+ - **Sticky headers**: When combining multiple sticky elements (e.g., hero + tabs), wrap them in **one parent div** with `position: sticky` instead of making each element sticky individually.
111
+ - **Tab navigation vs Accordion**: For 5+ sections of data, prefer horizontal `v-tabs` over `v-expansion-panels`. Tabs show one section at a time, reduce cognitive overload, and support swipe gestures on mobile.
112
+ - **Swipe gestures**: Attach `touchstart`/`touchend` listeners on the **outermost wrapper** (not on content area) so swipe works regardless of content height.
@@ -0,0 +1,455 @@
1
+ # FUI Vue Component Design Rules
2
+
3
+ > File này sở hữu: **quy tắc viết `uc-*.vue`: scope module/project, cấm `name:`/`components:{}`, props/emits/slots, cấm `_injectStyle`, quy tắc scope thủ công cho `<style scoped>`, quy trình kiểm chứng component (`component_preview` + luật push component cấp project trước khi render module), và quy ước `{projectId}/COMPONENTS.md` (tra component project-scope đã có + vòng tích luỹ)**. Bộ class/prop được duyệt xem [controls-styling-vocabulary.md](controls-styling-vocabulary.md).
4
+
5
+ Quy tắc bắt buộc khi viết bất kỳ Vue component nào (`uc-*.vue`) trong hệ thống FUI. Áp dụng cho cả component thông thường lẫn dialog component.
6
+
7
+ ---
8
+
9
+ ## 1. Hai cấp component: module-scope vs project-scope
10
+
11
+ | | Module-scope | Project-scope (global) |
12
+ |---|---|---|
13
+ | **Vị trí file** | `{projectId}/modules/{moduleId}/components/uc-*.vue` | `{projectId}/components/uc-*.vue` |
14
+ | **Registry** | `modules/{moduleId}/components/_components.json` | `{projectId}/components/_components.json` |
15
+ | **Phạm vi** | Chỉ trong module đó | **Tất cả modules trong project** |
16
+ | **Auto-load** | Trong module đã có component | Tự động — **không cần khai báo gì thêm** |
17
+ | **Khi nào dùng** | Component chỉ dùng ở một module | Component dùng lại ở nhiều module |
18
+
19
+ > **Project-scope component được FUI tự động load ở cấp global** — bất kỳ module nào trong project đều dùng được `<uc-*>` ngay, không cần tạo lại hay khai báo thêm.
20
+
21
+ ### Quy tắc chọn scope
22
+
23
+ ```
24
+ Component này sẽ dùng ở nhiều module? → project-scope
25
+ Component chỉ dùng trong một module? → module-scope
26
+ ```
27
+
28
+ ### Bảng thao tác theo scope
29
+
30
+ | Thao tác | Module-scope | Project-scope (global) |
31
+ |---|---|---|
32
+ | **Tạo mới** (chưa có trên server) | `component_new(componentName)` — dùng session.moduleId | `component_new(componentName, projectId=<pid>)` — KHÔNG có moduleId |
33
+ | **Cập nhật** (đã có trên server) | `component_update(componentName)` — dùng session.moduleId | `component_update(componentName, projectId=<pid>)` — KHÔNG có moduleId |
34
+ | **Xóa** | `component_delete(componentName)` — dùng session.moduleId | `component_delete(componentName, projectId=<pid>)` — KHÔNG có moduleId |
35
+ | **Đọc source** | `component_get(projectId, moduleId, componentName)` | `component_get(projectId, componentName)` — bỏ moduleId |
36
+ | **Sync danh sách** | `component_list(projectId, moduleId)` | `component_list(projectId)` — bỏ moduleId |
37
+ | **File trong workspace** | `{moduleDir}/components/uc-foo.vue` | `{projectDir}/components/uc-foo.vue` |
38
+
39
+ **Quy tắc duy nhất:** `moduleId` có → module-scope · `moduleId` vắng, chỉ `projectId` → project-scope
40
+
41
+ **Tạo và cập nhật là HAI tool riêng — không có upsert ngầm.** Cả hai đọc file `.vue` **local** (file phải tồn tại sẵn trên đĩa) và tự suy `ComID` từ `_components.json`, không truyền tay:
42
+
43
+ - `component_new` — chỉ INSERT. Làm mới `_components.json` **từ server trước** rồi mới kiểm tra tên; trùng tên = **lỗi cứng**. (Cache cũ mà cứ INSERT thì server sinh **hai record cùng tên**, không sửa được từ MCP.) Sau khi tạo tự sync lại để lấy `ComID` thật.
44
+ - `component_update` — chỉ UPDATE. Không tra được `ComID` = **lỗi cứng**, không âm thầm tạo mới: hoặc component chưa từng được tạo (→ `component_new`), hoặc cache local cũ (→ `component_list` rồi thử lại).
45
+
46
+ **`component_delete` tự lookup ComID** từ `_components.json` local. Nếu danh sách lệch, chạy `component_list` trước.
47
+
48
+ **`module_publish_html` không bao gồm project-scope component** — nếu có thay đổi component global, phải gọi `component_new`/`component_update` riêng trước hoặc sau.
49
+
50
+ ---
51
+
52
+ ## 1a. Tra cứu component project-scope đã có — `{projectId}/COMPONENTS.md`
53
+
54
+ Trước khi thiết kế một component cấp project mới, **phải kiểm tra `{projectId}/COMPONENTS.md`** (nếu có) — file này liệt kê các `uc-*.vue` cấp project đã tồn tại, kèm tóm tắt mục đích và props chính. Dựng lại component đã có chỉ gây trùng lặp và khó bảo trì.
55
+
56
+ ```
57
+ {FUI_MCP_WORKDIR}/{projectId}/COMPONENTS.md ← kiểm tra TRƯỚC khi tạo component project-scope mới
58
+ ```
59
+
60
+ **Vòng tích luỹ:** sau khi tạo/cập nhật một component cấp project có ý định dùng lại ở nhiều module, nối thêm một mục vào `{projectId}/COMPONENTS.md`. Chưa có file thì tạo mới theo khuôn:
61
+
62
+ ```markdown
63
+ # {projectId} — Danh sách component project-scope
64
+
65
+ ## uc-ten-component
66
+
67
+ **Mục đích:** mô tả ngắn gọn component dùng để làm gì
68
+ **Props:** `propA` (kiểu, mặc định) · `propB` (kiểu, mặc định)
69
+ **Events:** `@eventName` — khi nào phát
70
+ **Ghi chú:** điều gì cần biết khi dùng (nếu có)
71
+ ```
72
+
73
+ > Khuôn này theo đúng `{projectId}/DESIGN.md` (vòng tích luỹ quy tắc thẩm mỹ — xem [design-modes.md](design-modes.md) §2-3). File `COMPONENTS.md` **không bao giờ tự xoá mục** — xoá khi đã dùng `component_delete` bỏ component đó khỏi server.
74
+
75
+ ---
76
+
77
+ ## 2. Đặt tên và cấu trúc file
78
+
79
+ - **Prefix bắt buộc**: `uc-` (User Component) — phân biệt với `f-*` (FUI runtime) và `v-*` (Vuetify)
80
+ - **Format**: kebab-case — ví dụ `uc-trangthai-sukien.vue`, `uc-user-profile.vue`
81
+ - **Vị trí**: chỉ đặt trong `components/` ở root module hoặc root project, không tạo subfolder
82
+ - **Registry**: khai báo trong `components/_components.json` (chỉ cần `comName`, không tự đặt `comID`)
83
+
84
+ ---
85
+
86
+ ## 3. Đăng ký component — KHÔNG dùng `components: {}`, KHÔNG dùng `name:`
87
+
88
+ FUI tự động scan và đăng ký toàn bộ `uc-*.vue` ở cấp **global**. Tuyệt đối không khai báo `components: { ... }` bên trong một component — nó không hoạt động trong môi trường FUI.
89
+
90
+ Không khai báo thuộc tính `name:` trong `export default` — FUI tự xử lý tên component từ tên file khi tạo script, khai báo thêm là thừa và có thể gây xung đột:
91
+
92
+ ```js
93
+ // ❌ SAI — không cần và có thể gây xung đột
94
+ export default {
95
+ name: 'uc-user-card',
96
+ props: { ... }
97
+ }
98
+
99
+ // ✅ ĐÚNG — bỏ name, FUI tự resolve từ tên file
100
+ export default {
101
+ props: { ... }
102
+ }
103
+ ```
104
+
105
+ ```js
106
+ // ❌ SAI — không dùng trong FUI
107
+ export default {
108
+ components: { 'uc-user-card': UcUserCard },
109
+ }
110
+
111
+ // ✅ ĐÚNG — dùng trực tiếp tag trong template
112
+ // <uc-user-card :data="data" />
113
+ ```
114
+
115
+ ---
116
+
117
+ ## 4. Props
118
+
119
+ - Khai báo trong `script`: camelCase (`userName`, `itemList`)
120
+ - Dùng trong `template`: kebab-case (`:user-name="userName"`)
121
+ - Default của Array/Object phải dùng `function()`, không dùng arrow function:
122
+
123
+ ```js
124
+ // ❌ SAI
125
+ props: { items: { default: () => [] } }
126
+
127
+ // ✅ ĐÚNG
128
+ props: { items: { default: function() { return [] } } }
129
+ ```
130
+
131
+ - Ưu tiên tên props trung lập và tái sử dụng được: `items`, `value`, `label`, `loading`, `readonly`, `disabled`, `options`, `config`
132
+ - Không đặt tên props gắn chặt với một màn hình cụ thể nếu component có thể dùng lại
133
+
134
+ ---
135
+
136
+ ## 5. Emits
137
+
138
+ - Luôn emit lên parent thay vì mutate trực tiếp state của parent
139
+ - Các event chuẩn: `input`, `change`, `select`, `submit`, `remove`, `action`
140
+ - Dialog component luôn emit `('input', false)` để đóng — tương thích với `v-model` Vue 2
141
+
142
+ ---
143
+
144
+ ## 6. Template syntax — các ràng buộc bắt buộc
145
+
146
+ ### Không dùng backtick trong `<template>`
147
+
148
+ Template strings (backtick) không hoạt động đúng trong Vue 2 template. Dùng string concatenation:
149
+
150
+ ```html
151
+ <!-- ❌ SAI -->
152
+ :label="`Total (${items.length})`"
153
+
154
+ <!-- ✅ ĐÚNG -->
155
+ :label="'Total (' + items.length + ')'"
156
+ ```
157
+
158
+ ### Không dùng `@` shorthand cho events trong JSON
159
+
160
+ Trong `module.json` luôn dùng `v-on:click`, không dùng `@click`. Trong `.vue` template thì `@click` vẫn hợp lệ.
161
+
162
+ ### Vue 2 yêu cầu một root element duy nhất
163
+
164
+ ```html
165
+ <!-- ✅ ĐÚNG -->
166
+ <template>
167
+ <div>
168
+ <!-- nội dung -->
169
+ </div>
170
+ </template>
171
+ ```
172
+
173
+ ---
174
+
175
+ ## 7. Style & class — ƯU TIÊN class Vuetify có sẵn, KHÔNG tự chế
176
+
177
+ **Quy tắc: dùng class tiện ích Vuetify có sẵn cho MỌI nhu cầu spacing/flex/text/màu/kích thước. Chỉ định nghĩa class mới khi thật đặc biệt** (hiệu ứng/layout Vuetify không có). AI hay lạm dụng class tự chế → lãng phí, phá tính nhất quán, khó bảo trì.
178
+
179
+ ### ❌ TUYỆT ĐỐI KHÔNG inject CSS bằng JavaScript
180
+
181
+ Không tạo `<style>` bằng JS rồi chèn vào `document.head` (kiểu `_injectStyle`) — đây là anti-pattern nặng (vừa lách luật no-`<style>`, vừa tạo hàng chục class tự chế):
182
+
183
+ ```js
184
+ // ❌ SAI NGHIÊM TRỌNG — không bao giờ làm thế này trong .vue
185
+ methods: {
186
+ _injectStyle: function() {
187
+ var s = document.createElement('style')
188
+ s.textContent = ['.smg-root{...}', '.smg-card{...}', '.smg-badge{...}', ...].join('')
189
+ document.head.appendChild(s) // ❌ bơm CSS + hàng loạt class tự chế
190
+ }
191
+ }
192
+ ```
193
+
194
+ `<style scoped>` trong `.vue` được phép dùng cho CSS tùy chỉnh (hiếm khi cần) — nhưng `scoped` **không có tác dụng scoping thật**: FUI chỉ nối text `ComCSS` của mọi component trong module thành MỘT stylesheet chung (`ComponentCSSBuild()`), không compile SFC, không gắn `data-v-xxxx`. Vì vậy **bắt buộc tự scope thủ công**:
195
+
196
+ 1. Thẻ gốc trong `<template>` mang một class định danh duy nhất theo tên component (kebab-case) — ví dụ component `uc-invoice-list` thì thẻ gốc `class="uc-invoice-list"`.
197
+ 2. MỌI selector trong `<style scoped>` phải là descendant bắt đầu từ class đó — **không viết selector trần** (`.title`, `.header`...) vì sẽ leak sang mọi component khác cùng module.
198
+
199
+ ```vue
200
+ <template>
201
+ <div class="uc-invoice-list">
202
+ <div class="uc-invoice-list__toolbar">
203
+ <span class="uc-invoice-list__title">{{ title }}</span>
204
+ </div>
205
+ <f-table class="uc-invoice-list__table" :items="rows"></f-table>
206
+ </div>
207
+ </template>
208
+
209
+ <style scoped>
210
+ .uc-invoice-list__toolbar { display: flex; align-items: center; justify-content: space-between; }
211
+ .uc-invoice-list__title { font-weight: 600; font-size: 14px; }
212
+ .uc-invoice-list__table { margin-top: 8px; }
213
+ </style>
214
+ ```
215
+
216
+ ```vue
217
+ <!-- ❌ SAI — selector trần, đụng với component khác cùng module có class .title/.toolbar -->
218
+ <style scoped>
219
+ .title { font-weight: 600; }
220
+ .toolbar { display: flex; }
221
+ </style>
222
+ ```
223
+
224
+ CSS toàn-module (không gắn riêng component nào) → đặt trong `style.css` (publish qua `module_publish_css`), không phải `header.html`. `header.html` vẫn hợp lệ nhưng không còn là lựa chọn duy nhất.
225
+
226
+ ### Trước khi đặt một class mới → tra bộ từ vựng đã duyệt
227
+
228
+ Bảng đầy đủ class/prop Vuetify được phép dùng (spacing, flex, text, màu, bo góc, elevation,
229
+ breakpoint, kích thước) + thứ tự ưu tiên + điều kiện được định nghĩa class riêng:
230
+ → [controls-styling-vocabulary.md](controls-styling-vocabulary.md) §3.
231
+
232
+ Bộ từ vựng đó áp dụng y nguyên trong `<template>` của `.vue` — **khác biệt duy nhất**: trong `.vue`
233
+ không có `w`/`col` của grid FUI (đó là cú pháp của `module.json`, runtime FUI dịch ra class), nên
234
+ phải viết grid Vuetify trực tiếp.
235
+
236
+ **Cú pháp grid KHÁC NHAU giữa hai framework** — FUI V2 dùng Vuetify 1.5, FUI V3 dùng Vuetify 3
237
+ (canonical: [controls-patterns.md](controls-patterns.md) §Vuetify version):
238
+
239
+ | | V2 (Vuetify 1.5) | V3 (Vuetify 3) |
240
+ |---|---|---|
241
+ | Cách chia cột | `v-layout` > `v-flex xs8` | `v-row` > `v-col cols="8"` |
242
+ | `v-flex` | ✅ | ❌ **không tồn tại** — thẻ rơi xuống thành phần tử HTML trơ: không lỗi, không `[Vue warn]`, chỉ là **layout mất hẳn** |
243
+ | `v-layout` | ✅ hàng của grid | ⚠️ **có tồn tại nhưng nghĩa khác hẳn**: là wrapper bố cục ứng dụng (đi với `v-main`, như `fastproject-3.0.js` dùng), KHÔNG phải hàng của grid. Dùng nó thay `v-row` thì được một flex container không có gutter/breakpoint của grid |
244
+ | prop trần `shrink` | ✅ (prop của `v-flex`) | ❌ không có prop đó → rơi thành attribute DOM trơ. Dùng `class="shrink"` |
245
+
246
+ Template chuẩn ở mục 8 ngay dưới viết theo **V3** (`v-row`/`v-col`). Component viết cho project V2 thì
247
+ đổi sang `v-layout`/`v-flex` tương ứng.
248
+
249
+ Đây là lỗi hỏng-âm-thầm theo cả hai chiều: component dùng `v-flex` đưa sang project V3 sẽ **hiện đủ
250
+ nội dung nhưng sai bố cục**, và không tầng nào báo. Sửa component cũ thì kiểm bằng `component_preview`
251
+ ở **đúng framework của project** trước khi push.
252
+
253
+ CSS tùy chỉnh riêng component (nếu thật sự cần) đặt trong `<style scoped>` của chính `.vue` đó, tự
254
+ scope thủ công theo quy tắc ở mục 7 phía trên — không đặt trong `header.html` trừ khi CSS đó dùng
255
+ chung cho nhiều component/toàn module.
256
+
257
+ ---
258
+
259
+ ## 8. Dialog chuẩn trong Vue component (`uc-*.vue`) — TEMPLATE CHUẨN HOÁ
260
+
261
+ Dùng `v-dialog` + `v-card` với **quy ước spacing/size cố định** dưới đây cho MỌI dialog form. Chỉ dùng class tiện ích Vuetify — không tự chế class, không `style`.
262
+
263
+ ```html
264
+ <v-dialog v-model="editDialog" max-width="600">
265
+ <v-card>
266
+ <!-- Tiêu đề: px-3 pt-2 pb-3, subtitle-1 font-weight-bold, nút X bên phải -->
267
+ <v-card-title class="d-flex align-center px-3 pt-2 pb-3 subtitle-1 font-weight-bold">
268
+ {{ editItem.RoundID ? 'Chỉnh sửa vòng thi' : 'Thêm vòng thi mới' }}
269
+ <v-spacer></v-spacer>
270
+ <v-btn icon @click="editDialog = false"><v-icon>mdi-close</v-icon></v-btn>
271
+ </v-card-title>
272
+
273
+ <!-- Nội dung: px-3 py-1; field dùng outlined dense hide-details, chia cột bằng v-row/v-col -->
274
+ <v-card-text class="px-3 py-1">
275
+ <v-row>
276
+ <v-col cols="8">
277
+ <v-text-field v-model="editItem.RoundName" label="Tên vòng thi"
278
+ hide-details outlined dense></v-text-field>
279
+ </v-col>
280
+ <v-col cols="4">
281
+ <v-text-field v-model.number="editItem.RoundOrder" label="Thứ tự" type="number"
282
+ hide-details outlined dense></v-text-field>
283
+ </v-col>
284
+ </v-row>
285
+ </v-card-text>
286
+
287
+ <!-- Hàng cuối: action căn phải, button rộng tự nhiên; action phụ text, action chính primary -->
288
+ <v-card-actions class="pa-3">
289
+ <v-spacer></v-spacer>
290
+ <v-btn text @click="resetForm">Đặt lại</v-btn>
291
+ <v-btn color="primary" elevation="0" :loading="saving" @click="saveRound">Lưu</v-btn>
292
+ </v-card-actions>
293
+ </v-card>
294
+ </v-dialog>
295
+ ```
296
+
297
+ ### Quy ước bắt buộc (không đổi tuỳ tiện)
298
+
299
+ | Phần | Class/prop chuẩn | Ghi chú |
300
+ |---|---|---|
301
+ | `v-dialog` | `max-width="600"` | 600 cho form thường; 400 nhỏ; 800–900 form rộng |
302
+ | `v-card-title` | `d-flex align-center px-3 pt-2 pb-3 subtitle-1 font-weight-bold` | Tiêu đề gọn + nút X (`v-btn icon` → `mdi-close`) bên phải qua `v-spacer` |
303
+ | `v-card-text` | `px-3 py-1` | Padding nội dung nhất quán |
304
+ | Field nhập | `outlined dense hide-details` | Gọn, cùng chiều cao; chia cột bằng `v-row` + `v-col cols="N"` |
305
+ | `v-card-actions` | Là **hàng cuối**; `pa-3` + `v-spacer` trước nhóm nút | Toàn bộ action căn phải |
306
+ | Chiều ngang nút | Không đặt `block`, `width`, class grow hay bọc trong cột giãn | Giữ chiều ngang tự nhiên theo nhãn |
307
+ | Nút chính | Tối đa một nút dùng `color="primary" elevation="0"` + `:loading="saving"` | Có nền để thể hiện ưu tiên; `:loading` khi đang gọi API |
308
+ | Nút phụ | `text`, không `color` nền | Dùng cho action ít quan trọng; giao diện nhã nhặn, tối giản |
309
+
310
+ - Tiêu đề động theo chế độ: `{{ editItem.ID ? 'Chỉnh sửa' : 'Thêm mới' }}`.
311
+ - Số nhập dùng `v-model.number` + `type="number"`.
312
+ - Đóng dialog: `@click="dialogVar = false"`; sau khi lưu thành công cũng set `false` trong callback.
313
+ - Icon giữ kích thước mặc định; không tự thêm `small`/`:small` cho `v-icon` hoặc nút icon.
314
+
315
+ ### Biến thể
316
+
317
+ - **Toolbar header** (khi cần màu/icon nổi bật): thay `v-card-title` bằng `v-toolbar dense flat` + `v-toolbar-title`.
318
+ - **Fullscreen** (form lớn/nhiều vùng): `<v-dialog v-model="..." fullscreen>` + `v-toolbar` header, nội dung trong `v-container class="grid-list-md"`.
319
+
320
+ ---
321
+
322
+ ## 9. Thiết kế cho tái sử dụng
323
+
324
+ - Kiểm tra xem component có thể là: list, card, dialog body, filter panel, summary block, form section không
325
+ - Expose loading/empty/disabled/readonly qua props hoặc slots thay vì hardcode một workflow
326
+ - Giữ API calls, routing, permissions trong `module.json` hoặc parent — component chỉ lo presentation
327
+ - Khi module.json `controls` vượt ~200 dòng: extract UI sang Vue component, giữ module.json chỉ có data/API
328
+
329
+ ---
330
+
331
+ ## 10. Không nhúng `f-dialog` vào template component
332
+
333
+ `f-dialog` chỉ hoạt động khi khai báo trực tiếp trong `controls` JSON của module. Nếu cần dialog trong một `uc-*.vue`, dùng cấu trúc v-dialog ở mục 7 thay thế.
334
+
335
+ ---
336
+
337
+ ## 11. Giới hạn cú pháp trong `<script>` của component
338
+
339
+ ### Không dùng `async / await / finally`
340
+
341
+ FUI parser không hỗ trợ các keyword này bên trong file `.vue`. Dùng callback thay thế:
342
+
343
+ ```js
344
+ // ❌ SAI
345
+ async mounted() {
346
+ const res = await fetch(url)
347
+ }
348
+
349
+ // ✅ ĐÚNG
350
+ mounted() {
351
+ ajaxCALL(url, {}, function(res) { ... })
352
+ }
353
+ ```
354
+
355
+ ### Không dùng template literals (backtick) trong `<script>`
356
+
357
+ Backtick chỉ hoạt động trong `script.js` độc lập, không dùng được trong file `.vue`:
358
+
359
+ ```js
360
+ // ❌ SAI — trong .vue <script>
361
+ var url = `/api/Student/${this.studentID}`
362
+
363
+ // ✅ ĐÚNG
364
+ var url = '/api/Student/' + this.studentID
365
+ ```
366
+
367
+ ---
368
+
369
+ ## 12. Tận dụng hàm có sẵn trong `defaultfunction.js` — KHÔNG viết lại
370
+
371
+ FUI đã cung cấp sẵn nhiều hàm global. **Trước khi tự viết logic (gọi API, xác nhận, thông báo, format, group, copy...) → kiểm tra [default-function.md](default-function.md) xem đã có chưa.** Chỉ tự viết khi hàm có sẵn không đáp ứng được. Viết lại thứ đã có = lãng phí + không nhất quán.
372
+
373
+ ### Gọi API — dùng `ajaxCALL` (KHÔNG `fetch`/`axios`/`$.ajax` thủ công)
374
+
375
+ `fetch` bị lỗi CORS/credentials sang domain khác. `ajaxCALL` tự xử lý credentials, token (Bearer), và base domain (`v_Set.apiDomain`) — không cần ghép domain.
376
+
377
+ ```js
378
+ // ❌ SAI
379
+ fetch(url, { method: 'POST', body: JSON.stringify(data) })
380
+ .then(r => r.json()).then(res => { ... })
381
+
382
+ // ✅ ĐÚNG — ajaxCALL(URL, DATA, successCb, errorCb, header)
383
+ ajaxCALL('spAPI_RoundList', { ContestID: this.contestID }, function(res) {
384
+ // res đã parse JSON (json_data tự parse)
385
+ }, function(err) {
386
+ // tuỳ chọn — mặc định ajaxCALL đã showMessage lỗi
387
+ })
388
+ ```
389
+
390
+ ### Xác nhận — dùng `confirm` (KHÔNG `window.confirm`)
391
+
392
+ ```js
393
+ confirm({
394
+ title: 'Xác nhận xoá',
395
+ message: 'Bạn có chắc muốn xoá vòng thi này?',
396
+ action: function() { ajaxCALL('spAPI_RoundDelete', { RoundID: id }, function(){ ... }); }
397
+ });
398
+ ```
399
+
400
+ ### Thông báo — dùng `showMessage` hoặc `Vue.$toast`
401
+
402
+ ```js
403
+ showMessage({ title: 'Đã lưu thành công' }); // hộp thoại
404
+ showMessage({ title: 'Lỗi', message: '...', type: 'red' });
405
+ Vue.$toast.success('Đã lưu', { position: 'top' }); // toast góc màn hình
406
+ ```
407
+
408
+ ### Các hàm hay dùng khác (đừng viết lại)
409
+
410
+ `groupBy` · `findInArray` · `jsonToExcel` (xuất Excel) · `printPDF` · `copyToClipboard` · `openWindow` · `pushRouter` · `rightTest` (kiểm tra quyền) · `generateID` · `capacityText` · `buildHeader` (tự sinh headers f-table). Chi tiết: [default-function.md](default-function.md).
411
+
412
+ > Thư viện toàn cục cũng có sẵn — dùng thay vì tự cài: `_` (Lodash: `_.groupBy`, `_.sumBy`, `_.cloneDeep`...), `moment` (ngày giờ), `numeral` (format số). Không thêm CDN cho các thư viện này.
413
+
414
+ ---
415
+
416
+ ## 13. Kiểm chứng component — `component_preview` TRƯỚC khi ráp vào module
417
+
418
+ Một `uc-*.vue` là đơn vị đầy đủ (template + script + style) nhưng nếu chỉ nhìn thấy nó sau khi đã ráp vào module thì lỗi của component và lỗi lắp ráp trộn lẫn — mất thời gian khoanh vùng. `component_preview` render **đúng một component** trên một trang riêng, bằng file `.vue` **local** (chưa cần push), với dữ liệu mẫu bạn truyền thẳng trong tham số.
419
+
420
+ ```
421
+ component_preview({
422
+ componentName: "uc-invoice-list",
423
+ projectId: "...", // chỉ projectId → project-scope
424
+ // moduleId: "..." // có moduleId → module-scope (suy luận y hệt component_new/component_update)
425
+ cases: [
426
+ { name: "danh sách rỗng", props: { items: [] } },
427
+ { name: "có dữ liệu", props: { items: [ {...}, {...} ], title: "Hoá đơn" } },
428
+ { name: "click chọn dòng", props: { items: [...] },
429
+ scenario: [ { click: "Xem" }, { assert: "vueData.selected != null" } ] }
430
+ ],
431
+ devices: ["desktop", "mobile"] // mặc định
432
+ })
433
+ ```
434
+
435
+ - **Props**: mảng/object cứ truyền **giá trị thật** — tool tự đẩy vào `data[]` và bind `:prop`. Chuỗi thành attr tĩnh; key đã bắt đầu bằng `:`/`v-` giữ nguyên văn (dùng `v-on:`/`v-slot:`, **không** `@`/`#`).
436
+ - **`cases × devices` ≤ 8**. Mỗi lần render là một phiên browser thật.
437
+ - `slot` (nội dung `innerHTML`), `vueData`, `apiMocks`, `user` (test permission gate), `viewport` (ép chiều cao thấp để lộ component tự đo chiều cao) đều truyền được.
438
+ - `scenario` dùng **đúng grammar renderUI trên DOM thật**: `set`/`click`/`call`/`exe`/`assert`, chọn dòng bằng `nth` (không có `item`/`index`).
439
+ - **`style.css` của module KHÔNG được nạp** — đó chính là ý nghĩa "không gian riêng": component phải tự đứng được bằng `<style>` của chính nó + theme project.
440
+ - Trả về: ảnh chụp (tối đa 4 inline, còn lại ghi đường dẫn) + JS error + `[Vue warn]` + DOM audit. **Đọc ảnh rồi tự sửa** trước khi push.
441
+
442
+ ### Component cấp project: BẮT BUỘC push trước khi render module dùng nó
443
+
444
+ `module_simulate({ renderUI: true })` dựng tầng project bằng bundle **tải từ server**. Bản local chưa push sẽ **không xuất hiện** — ảnh render là bản cũ trên server, tức ảnh nói dối. Vì vậy renderUI **chặn cứng** (không có cờ bỏ qua) khi component cấp project ở local lệch với bundle server.
445
+
446
+ ```
447
+ sửa uc-*.vue cấp project
448
+ → component_preview (kiểm chứng bản local, chưa push)
449
+ → component_new (lần đầu) hoặc component_update (các lần sau) — push lên server
450
+ → module_simulate({ renderUI: true }) ← chỉ chạy được sau khi đã push
451
+ ```
452
+
453
+ Nếu bị chặn mà **bản trên server mới hơn** (ai đó vừa sửa trên web IDE): chạy `project_sync` để lấy về, **đừng push đè** — sẽ mất bài của người khác.
454
+
455
+ Component **cấp module** không bị luật này: renderUI dựng tầng module từ file local nên sửa tới đâu render thấy tới đó.
@@ -0,0 +1,77 @@
1
+ # FUI Component Quick Reference
2
+
3
+ > File này sở hữu: **danh mục component FUI, bảng component cần import JS/CSS, quy trình thêm/bỏ import**. Chi tiết từng component xem file reference tương ứng ở cột phải.
4
+
5
+ Tra cứu nhanh toàn bộ component có sẵn trong FUI runtime. Ưu tiên dùng `f-*` trước — chỉ dùng `v-*` khi không có FUI equivalent.
6
+
7
+ | Component | Dùng khi nào | Reference |
8
+ |---|---|---|
9
+ | `f-table` | **Hiển thị dữ liệu dạng datatable** — cả CRUD qua dialog form lẫn chỉ xem (readonly = không khai `:update-api`); auto-build headers khi không truyền `:headers`; hàng tổng qua `sum-format` | [component-table.md](component-table.md) |
10
+ | `f-dialog` | Modal dialog với form, watch, button | [components-dialog.md](components-dialog.md) |
11
+ | `f-window` | Dialog mở URL trong iframe | [components-dialog.md](components-dialog.md) |
12
+ | `f-button` | Nút bấm có action, hotkey, form validation | [components-input.md](components-input.md) |
13
+ | `f-date` | Input ngày có date picker + typed mask | [components-input.md](components-input.md) |
14
+ | `f-time` | Input giờ có time picker + typed mask | [components-input.md](components-input.md) |
15
+ | `f-time-counter` | Đếm ngược / đồng hồ | [components-input.md](components-input.md) |
16
+ | `f-search` | Autocomplete tìm kiếm qua API | [components-input.md](components-input.md) |
17
+ | `f-menu` | Dropdown button menu có icon/action/link | [components-input.md](components-input.md) |
18
+ | `f-radiobox` | Radio group từ array items | [components-input.md](components-input.md) |
19
+ | `f-file-upload` | Upload file (Plupload, progress dialog) | [components-input.md](components-input.md) |
20
+ | `f-image-update` | Upload + crop ảnh | [components-input.md](components-input.md) |
21
+ | `f-excel-reader` | Đọc file Excel/CSV → JSON (chọn sheet, bỏ N dòng đầu, tự đặt tên cột) | [components-input.md](components-input.md) |
22
+ | `f-qrcode` | Render mã QR | [components-input.md](components-input.md) |
23
+ | `f-qrcode-reader` | Đọc QR bằng camera | [components-input.md](components-input.md) |
24
+ | `f-editor` | Rich text editor (CKEditor 5) | [components-dialog.md](components-dialog.md) |
25
+ | `f-editor-dialog` | Rich text editor trong modal | [components-dialog.md](components-dialog.md) |
26
+ | `f-echart` | **Biểu đồ** — ECharts V5: bar, line, pie, scatter, gauge, radar, heatmap, sankey, tree, map, graph... | [components-echart.md](components-echart.md) · [echart-templates.md](echart-templates.md) |
27
+ | `f-sheet` | **Nhập liệu trực tiếp trên bảng kiểu Excel** — sửa ô tại chỗ, dán từ Excel, không qua dialog (dựa trên AG Grid) — **cần import** | [fsheet.md](fsheet.md) |
28
+ | `f-pdfmake` | Render PDF trong iframe | [components-display.md](components-display.md) · [pdfmake.md](pdfmake.md) |
29
+ | `f-label` | Chip/badge hiển thị tag, trạng thái | [components-display.md](components-display.md) |
30
+ | `f-box` | Hiển thị/chỉnh sửa key-value rows | [components-display.md](components-display.md) |
31
+ | `f-header` | Tiêu đề lớn (display-1, primary color) | [components-display.md](components-display.md) |
32
+ | `f-title` | Tiêu đề nhỏ (title, primary color) | [components-display.md](components-display.md) |
33
+ | `f-slider` | Carousel ảnh | [components-display.md](components-display.md) |
34
+ | `fp-profile` | Khối thông tin/hồ sơ user | [components-display.md](components-display.md) |
35
+
36
+ ### Component trong ô bảng (`t-*`) — dùng trong `headers` của f-table
37
+
38
+ `t-html` · `t-label` · `t-num` · `t-time` · `t-boolean` · `t-check` (toggle) · `t-text` · `t-select` · `t-combobox` · `t-menu` (context menu) · `t-link` · `t-button` — chi tiết & ví dụ: [ui-table-cell-patterns.md](ui-table-cell-patterns.md).
39
+
40
+ > `header-bar` và `f-window` là component nội bộ do runtime tự dựng (menu header, cửa sổ `openWindow`) — không đặt trực tiếp trong `controls`.
41
+
42
+ ## Components cần import JS/CSS
43
+
44
+ Một số component yêu cầu import thêm thư viện — **phải gọi `file_import_list` để kiểm tra và thêm/xóa import tương ứng khi thêm hoặc bỏ component**.
45
+
46
+ | Component | Import bắt buộc |
47
+ |---|---|
48
+ | `f-pdfmake` | `/include/pdfmake/pdfmake.min.js` · `/include/pdfmake/print.min.js` · `/include/pdfmake/vfs_fonts.js` |
49
+ | `f-file-upload` | `/include/plupload-2.3.7/plupload.full.min.js` |
50
+ | `f-image-update` | `/include/vue-advanced-cropper/index.umd.js` · `/include/vue-advanced-cropper/style.css` |
51
+ | `f-qrcode` | `/include/qrcode/qrcode.min.js` |
52
+ | `f-qrcode-reader` | `/include/qrcode/qrcode-reader.min.js` |
53
+ | `f-echart` | `/include/chart/echarts.min.js` · `/include/chart/fechart.js` (**theo thứ tự này**) — nền tảng Apache ECharts |
54
+ | `f-sheet` | `/include/ag-grid/ag-grid-community.min.js` · `/include/ag-grid/fsheet.js` (**theo thứ tự này**) — nền tảng AG Grid |
55
+ | `f-editor` | `/include/ckeditor5/build/ckeditor.js` |
56
+ | `f-editor-dialog` | `/include/ckeditor5/build/ckeditor.js` |
57
+
58
+ > **Bundled sẵn, KHÔNG cần import thêm:** `v-*` (Vuetify) · `f-table` · `f-dialog` · `f-button` ·
59
+ > `f-date` · `f-time` · `f-search` · `f-menu` · `f-radiobox` · `f-box` · `f-label` · `f-header` ·
60
+ > `f-title` · `f-slider` · `f-excel-reader` — và mọi component không có tên trong bảng trên.
61
+
62
+ **Quy trình khi thêm component có import:**
63
+ 1. `file_import_list` (projectId + moduleId) — kiểm tra import hiện có
64
+ 2. Nếu chưa có → `file_import_new` để thêm từng file
65
+ 3. Nếu bỏ component → kiểm tra import đó còn dùng ở chỗ nào khác không; nếu không → `file_import_delete`
66
+
67
+ ---
68
+
69
+ ## Quy tắc chọn nhanh
70
+
71
+ - `f-button` thay `v-btn` khi cần action FUI (CALL, hotkey, form validation)
72
+ - `f-date` / `f-time` thay `v-text-field` + manual date parsing
73
+ - `f-search` thay `v-autocomplete` khi cần search qua API
74
+ - `f-menu` thay `v-menu + v-btn` khi cần dropdown actions
75
+ - `f-table` thay `v-data-table` cho MỌI bảng — CRUD lẫn chỉ xem (readonly = không khai `:update-api`)
76
+ - `f-echart` cho mọi loại biểu đồ (bar, line, pie, gauge, scatter, radar, sankey, tree, map...)
77
+ - Dialog: `ctrl-update` của `f-table` cho CRUD inline; dialog riêng → dựng `v-dialog` thủ công theo anatomy chuẩn (`design_read("fui")` §4 + `ui-dialog-patterns.md`); `f-dialog` chỉ cho form rất nhỏ