@ngockhoale/ukit 1.1.6

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 (344) hide show
  1. package/CHANGELOG.md +179 -0
  2. package/LICENSE +21 -0
  3. package/README.md +189 -0
  4. package/bin/ukit +30 -0
  5. package/manifests/platform.full.yaml +1194 -0
  6. package/package.json +71 -0
  7. package/scripts/bug/triage.mjs +37 -0
  8. package/scripts/index/build-index.mjs +35 -0
  9. package/scripts/index/query-index.mjs +92 -0
  10. package/scripts/index/refresh-index.mjs +85 -0
  11. package/scripts/release/verify-release.mjs +56 -0
  12. package/src/bug/triageBug.js +123 -0
  13. package/src/cli/adapters.js +148 -0
  14. package/src/cli/commands/diff.js +51 -0
  15. package/src/cli/commands/doctor.js +125 -0
  16. package/src/cli/commands/indexArgs.js +73 -0
  17. package/src/cli/commands/indexTools.js +509 -0
  18. package/src/cli/commands/install.js +293 -0
  19. package/src/cli/commands/memory.js +126 -0
  20. package/src/cli/commands/status.js +8 -0
  21. package/src/cli/commands/uninstall.js +51 -0
  22. package/src/cli/index.js +109 -0
  23. package/src/context/detectProjectContext.js +49 -0
  24. package/src/context/detectProviders.js +12 -0
  25. package/src/core/applyPlan.js +89 -0
  26. package/src/core/buildPlan.js +228 -0
  27. package/src/core/compact/index.js +294 -0
  28. package/src/core/compact/threshold.js +936 -0
  29. package/src/core/diffPlan.js +73 -0
  30. package/src/core/ensureGitignore.js +117 -0
  31. package/src/core/fileOps.js +188 -0
  32. package/src/core/memory/hygiene.js +160 -0
  33. package/src/core/memory/index.js +2 -0
  34. package/src/core/memory/retrieval.js +476 -0
  35. package/src/core/memory/store.js +202 -0
  36. package/src/core/metadata.js +132 -0
  37. package/src/core/migrateLegacy.js +139 -0
  38. package/src/core/output/index.js +1309 -0
  39. package/src/core/paths.js +13 -0
  40. package/src/core/report.js +17 -0
  41. package/src/core/router/advisor.js +42 -0
  42. package/src/core/router/index.js +2 -0
  43. package/src/core/router/router.js +164 -0
  44. package/src/core/runInstallPipeline.js +365 -0
  45. package/src/core/runtimeConfig.js +190 -0
  46. package/src/core/runtimePaths.js +24 -0
  47. package/src/core/status.js +186 -0
  48. package/src/core/token/index.js +328 -0
  49. package/src/core/uninstall.js +246 -0
  50. package/src/core/validation/confidence.js +89 -0
  51. package/src/core/validation/index.js +2 -0
  52. package/src/core/validation/validator.js +165 -0
  53. package/src/index/buildIndex.js +1392 -0
  54. package/src/index/gitHooks.js +109 -0
  55. package/src/index/importResolution.js +377 -0
  56. package/src/index/languageTools.js +127 -0
  57. package/src/index/paths.js +27 -0
  58. package/src/index/queryIndex.js +637 -0
  59. package/src/index/relatedTests.js +237 -0
  60. package/src/index/resolveContext.js +345 -0
  61. package/src/index/routeCatalog.js +258 -0
  62. package/src/index/taskRouting.js +677 -0
  63. package/src/index/verificationPlan.js +437 -0
  64. package/src/manifest/loadManifest.js +22 -0
  65. package/src/manifest/selectItems.js +78 -0
  66. package/src/manifest/validateManifest.js +115 -0
  67. package/src/render/buildVariables.js +39 -0
  68. package/src/render/renderTemplate.js +44 -0
  69. package/src/stack/detectStack.js +213 -0
  70. package/templates/.claude/agents/bug-debugger.md +57 -0
  71. package/templates/.claude/agents/feature-implementer.md +55 -0
  72. package/templates/.claude/config/providers.md +25 -0
  73. package/templates/.claude/hooks/auto-allow-bash.sh +155 -0
  74. package/templates/.claude/hooks/auto-prune-bash.sh +75 -0
  75. package/templates/.claude/hooks/block-dangerous.sh +54 -0
  76. package/templates/.claude/hooks/compress-output.sh +17 -0
  77. package/templates/.claude/hooks/protect-files.sh +37 -0
  78. package/templates/.claude/hooks/reinject-context.sh +28 -0
  79. package/templates/.claude/hooks/session-start.md +13 -0
  80. package/templates/.claude/hooks/skill-router.sh +1681 -0
  81. package/templates/.claude/hooks/verification-guard.sh +271 -0
  82. package/templates/.claude/settings.json +144 -0
  83. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  84. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  85. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  86. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  87. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  88. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  89. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  90. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  91. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  92. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  93. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  94. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  95. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  96. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  97. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  98. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  99. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  100. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  101. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  102. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  103. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  104. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  105. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  106. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  107. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  108. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  109. package/templates/.claude/skills/_shared/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  110. package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  111. package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  112. package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  113. package/templates/.claude/skills/_shared/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  114. package/templates/.claude/skills/_shared/ooxml/schemas/mce/mc.xsd +75 -0
  115. package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  116. package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  117. package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  118. package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  119. package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  120. package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  121. package/templates/.claude/skills/_shared/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  122. package/templates/.claude/skills/_shared/ooxml/scripts/pack.py +159 -0
  123. package/templates/.claude/skills/_shared/ooxml/scripts/unpack.py +29 -0
  124. package/templates/.claude/skills/_shared/ooxml/scripts/validate.py +69 -0
  125. package/templates/.claude/skills/_shared/ooxml/scripts/validation/__init__.py +15 -0
  126. package/templates/.claude/skills/_shared/ooxml/scripts/validation/base.py +951 -0
  127. package/templates/.claude/skills/_shared/ooxml/scripts/validation/docx.py +274 -0
  128. package/templates/.claude/skills/_shared/ooxml/scripts/validation/pptx.py +315 -0
  129. package/templates/.claude/skills/_shared/ooxml/scripts/validation/redlining.py +279 -0
  130. package/templates/.claude/skills/backend-api/SKILL.md +26 -0
  131. package/templates/.claude/skills/canvas-design/LICENSE.txt +202 -0
  132. package/templates/.claude/skills/canvas-design/SKILL.md +130 -0
  133. package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  134. package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  135. package/templates/.claude/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  136. package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  137. package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  138. package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  139. package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  140. package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  141. package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  142. package/templates/.claude/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  143. package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  144. package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  145. package/templates/.claude/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  146. package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  147. package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  148. package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  149. package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  150. package/templates/.claude/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  151. package/templates/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  152. package/templates/.claude/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  153. package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  154. package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  155. package/templates/.claude/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  156. package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  157. package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  158. package/templates/.claude/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  159. package/templates/.claude/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  160. package/templates/.claude/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  161. package/templates/.claude/skills/code-review/SKILL.md +97 -0
  162. package/templates/.claude/skills/debugging-toolkit/SKILL.md +156 -0
  163. package/templates/.claude/skills/delivery/SKILL.md +92 -0
  164. package/templates/.claude/skills/discover-security/SKILL.md +86 -0
  165. package/templates/.claude/skills/docker-packaging/SKILL.md +60 -0
  166. package/templates/.claude/skills/docs-manager/SKILL.md +465 -0
  167. package/templates/.claude/skills/docs-manager/init-project-docs.sh +70 -0
  168. package/templates/.claude/skills/docs-manager/templates/README.md.template +50 -0
  169. package/templates/.claude/skills/docs-manager/templates/agent-roles.md.template +24 -0
  170. package/templates/.claude/skills/docs-manager/templates/coding-conventions.md.template +28 -0
  171. package/templates/.claude/skills/docs-manager/templates/memory.md.template +30 -0
  172. package/templates/.claude/skills/docs-manager/templates/onboarding.md.template +20 -0
  173. package/templates/.claude/skills/docs-manager/templates/project.md.template +26 -0
  174. package/templates/.claude/skills/docs-quality/SKILL.md +148 -0
  175. package/templates/.claude/skills/docx/LICENSE.txt +30 -0
  176. package/templates/.claude/skills/docx/SKILL.md +197 -0
  177. package/templates/.claude/skills/docx/docx-js.md +350 -0
  178. package/templates/.claude/skills/docx/ooxml.md +610 -0
  179. package/templates/.claude/skills/docx/scripts/__init__.py +1 -0
  180. package/templates/.claude/skills/docx/scripts/document.py +1276 -0
  181. package/templates/.claude/skills/docx/scripts/templates/comments.xml +3 -0
  182. package/templates/.claude/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  183. package/templates/.claude/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  184. package/templates/.claude/skills/docx/scripts/templates/commentsIds.xml +3 -0
  185. package/templates/.claude/skills/docx/scripts/templates/people.xml +3 -0
  186. package/templates/.claude/skills/docx/scripts/utilities.py +374 -0
  187. package/templates/.claude/skills/duraone/SKILL.md +204 -0
  188. package/templates/.claude/skills/duraone/references/backend.md +636 -0
  189. package/templates/.claude/skills/duraone/references/frontend.md +1506 -0
  190. package/templates/.claude/skills/duraone/references/sql.md +631 -0
  191. package/templates/.claude/skills/duraone/references/workflow.md +520 -0
  192. package/templates/.claude/skills/executing-plans/SKILL.md +76 -0
  193. package/templates/.claude/skills/file-organizer/SKILL.md +433 -0
  194. package/templates/.claude/skills/frontend/SKILL.md +26 -0
  195. package/templates/.claude/skills/frontend-design/LICENSE.txt +177 -0
  196. package/templates/.claude/skills/frontend-design/SKILL.md +42 -0
  197. package/templates/.claude/skills/frontend-vue/SKILL.md +127 -0
  198. package/templates/.claude/skills/frontend-vue/components/Control/Box.vue +137 -0
  199. package/templates/.claude/skills/frontend-vue/components/Control/Button.vue +93 -0
  200. package/templates/.claude/skills/frontend-vue/components/Control/ButtonBar.vue +29 -0
  201. package/templates/.claude/skills/frontend-vue/components/Control/ButtonFloat.vue +62 -0
  202. package/templates/.claude/skills/frontend-vue/components/Control/CheckButton.vue +75 -0
  203. package/templates/.claude/skills/frontend-vue/components/Control/Checkbox.vue +58 -0
  204. package/templates/.claude/skills/frontend-vue/components/Control/Datetime.vue +148 -0
  205. package/templates/.claude/skills/frontend-vue/components/Control/Dropdownlist.vue +156 -0
  206. package/templates/.claude/skills/frontend-vue/components/Control/Input.vue +106 -0
  207. package/templates/.claude/skills/frontend-vue/components/Control/Label.vue +38 -0
  208. package/templates/.claude/skills/frontend-vue/components/Control/Master/BoxColumn.vue +24 -0
  209. package/templates/.claude/skills/frontend-vue/components/Control/Popup/Confirm.vue +33 -0
  210. package/templates/.claude/skills/frontend-vue/components/Control/Popup/Info.vue +32 -0
  211. package/templates/.claude/skills/frontend-vue/components/Control/Popup/ModalInfo.vue +39 -0
  212. package/templates/.claude/skills/frontend-vue/components/Control/Popup/Reject.vue +64 -0
  213. package/templates/.claude/skills/frontend-vue/components/Control/Tag.vue +82 -0
  214. package/templates/.claude/skills/frontend-vue/components/Control/Upload.vue +61 -0
  215. package/templates/.claude/skills/frontend-vue/components/ControlMobile/Dropdownlist.vue +103 -0
  216. package/templates/.claude/skills/frontend-vue/components/ControlMobile/PagingBar.vue +108 -0
  217. package/templates/.claude/skills/frontend-vue/components/ControlMobile/UploadImage.vue +137 -0
  218. package/templates/.claude/skills/frontend-vue/components/Grid/AG.vue +806 -0
  219. package/templates/.claude/skills/frontend-vue/components/Grid/AntTable.vue +253 -0
  220. package/templates/.claude/skills/frontend-vue/components/Grid/CustomDropdownEditor.vue +43 -0
  221. package/templates/.claude/skills/frontend-vue/components/Grid/CustomDropdownEditorEnable.vue +55 -0
  222. package/templates/.claude/skills/frontend-vue/components/Grid/HtmlTable.vue +40 -0
  223. package/templates/.claude/skills/frontend-vue/components/PDFViewer.vue +25 -0
  224. package/templates/.claude/skills/frontend-vue/components/Panel/FormView.vue +309 -0
  225. package/templates/.claude/skills/frontend-vue/components/Partial/Footer.vue +23 -0
  226. package/templates/.claude/skills/frontend-vue/components/Partial/Header.vue +265 -0
  227. package/templates/.claude/skills/frontend-vue/components/Partial/Sidebar.vue +122 -0
  228. package/templates/.claude/skills/frontend-vue/components/Template.vue +16 -0
  229. package/templates/.claude/skills/frontend-vue/components/View/Form.vue +89 -0
  230. package/templates/.claude/skills/frontend-vue/composables/indexDBStore.js +140 -0
  231. package/templates/.claude/skills/frontend-vue/composables/masterApi.js +362 -0
  232. package/templates/.claude/skills/frontend-vue/composables/state.js +578 -0
  233. package/templates/.claude/skills/frontend-vue/composables/useRequest.js +221 -0
  234. package/templates/.claude/skills/frontend-vue/composables/useSession.js +179 -0
  235. package/templates/.claude/skills/frontend-vue/composables/useTranslation.js +54 -0
  236. package/templates/.claude/skills/frontend-vue/composables/useWebSocket.js +257 -0
  237. package/templates/.claude/skills/frontend-vue/composables/userObj.js +111 -0
  238. package/templates/.claude/skills/frontend-vue/composables/utils.js +322 -0
  239. package/templates/.claude/skills/frontend-vue/reference/composables-example.vue +320 -0
  240. package/templates/.claude/skills/frontend-vue/reference/form-example.vue +183 -0
  241. package/templates/.claude/skills/frontend-vue/reference/grid-example.vue +147 -0
  242. package/templates/.claude/skills/frontend-vue/reference/masterdata-example/[id].vue +106 -0
  243. package/templates/.claude/skills/frontend-vue/reference/masterdata-example/index.vue +58 -0
  244. package/templates/.claude/skills/frontend-vue/reference/popup-example.vue +159 -0
  245. package/templates/.claude/skills/pdf/LICENSE.txt +30 -0
  246. package/templates/.claude/skills/pdf/SKILL.md +294 -0
  247. package/templates/.claude/skills/pdf/forms.md +205 -0
  248. package/templates/.claude/skills/pdf/reference.md +612 -0
  249. package/templates/.claude/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  250. package/templates/.claude/skills/pdf/scripts/check_bounding_boxes_test.py +226 -0
  251. package/templates/.claude/skills/pdf/scripts/check_fillable_fields.py +12 -0
  252. package/templates/.claude/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  253. package/templates/.claude/skills/pdf/scripts/create_validation_image.py +41 -0
  254. package/templates/.claude/skills/pdf/scripts/extract_form_field_info.py +152 -0
  255. package/templates/.claude/skills/pdf/scripts/fill_fillable_fields.py +114 -0
  256. package/templates/.claude/skills/pdf/scripts/fill_pdf_form_with_annotations.py +108 -0
  257. package/templates/.claude/skills/pdf-processing/SKILL.md +107 -0
  258. package/templates/.claude/skills/pdf-processing-pro/FORMS.md +610 -0
  259. package/templates/.claude/skills/pdf-processing-pro/OCR.md +137 -0
  260. package/templates/.claude/skills/pdf-processing-pro/SKILL.md +296 -0
  261. package/templates/.claude/skills/pdf-processing-pro/TABLES.md +626 -0
  262. package/templates/.claude/skills/pdf-processing-pro/scripts/analyze_form.py +307 -0
  263. package/templates/.claude/skills/postgres/SKILL.md +69 -0
  264. package/templates/.claude/skills/postgres/reference/fn_get_examples.sql +208 -0
  265. package/templates/.claude/skills/postgres/reference/fn_rpt_examples.sql +239 -0
  266. package/templates/.claude/skills/postgres/reference/utility_functions.sql +94 -0
  267. package/templates/.claude/skills/pptx/LICENSE.txt +30 -0
  268. package/templates/.claude/skills/pptx/SKILL.md +484 -0
  269. package/templates/.claude/skills/pptx/html2pptx.md +625 -0
  270. package/templates/.claude/skills/pptx/ooxml.md +427 -0
  271. package/templates/.claude/skills/pptx/scripts/html2pptx.js +979 -0
  272. package/templates/.claude/skills/pptx/scripts/inventory.py +1020 -0
  273. package/templates/.claude/skills/pptx/scripts/rearrange.py +231 -0
  274. package/templates/.claude/skills/pptx/scripts/replace.py +385 -0
  275. package/templates/.claude/skills/pptx/scripts/thumbnail.py +450 -0
  276. package/templates/.claude/skills/repo-maintenance/SKILL.md +97 -0
  277. package/templates/.claude/skills/research/EXAMPLES.md +434 -0
  278. package/templates/.claude/skills/research/REFERENCE.md +399 -0
  279. package/templates/.claude/skills/research/SKILL.md +136 -0
  280. package/templates/.claude/skills/root-cause-tracing/SKILL.md +174 -0
  281. package/templates/.claude/skills/root-cause-tracing/find-polluter.sh +63 -0
  282. package/templates/.claude/skills/sharing-skills/SKILL.md +194 -0
  283. package/templates/.claude/skills/sql-optimization-patterns/SKILL.md +493 -0
  284. package/templates/.claude/skills/subagent-driven-development/SKILL.md +189 -0
  285. package/templates/.claude/skills/systematic-debugging/CREATION-LOG.md +119 -0
  286. package/templates/.claude/skills/systematic-debugging/SKILL.md +295 -0
  287. package/templates/.claude/skills/systematic-debugging/test-academic.md +14 -0
  288. package/templates/.claude/skills/systematic-debugging/test-pressure-1.md +58 -0
  289. package/templates/.claude/skills/systematic-debugging/test-pressure-2.md +68 -0
  290. package/templates/.claude/skills/systematic-debugging/test-pressure-3.md +69 -0
  291. package/templates/.claude/skills/test-driven-development/SKILL.md +364 -0
  292. package/templates/.claude/skills/testing-anti-patterns/SKILL.md +302 -0
  293. package/templates/.claude/skills/testing-quality/SKILL.md +97 -0
  294. package/templates/.claude/skills/verification-before-completion/SKILL.md +139 -0
  295. package/templates/.claude/skills/webapp-testing/LICENSE.txt +202 -0
  296. package/templates/.claude/skills/webapp-testing/SKILL.md +96 -0
  297. package/templates/.claude/skills/webapp-testing/examples/console_logging.py +35 -0
  298. package/templates/.claude/skills/webapp-testing/examples/element_discovery.py +40 -0
  299. package/templates/.claude/skills/webapp-testing/examples/static_html_automation.py +33 -0
  300. package/templates/.claude/skills/webapp-testing/scripts/with_server.py +106 -0
  301. package/templates/.claude/ukit/index/build-index.mjs +28 -0
  302. package/templates/.claude/ukit/index/cache-utils.mjs +140 -0
  303. package/templates/.claude/ukit/index/lib/index-core.mjs +2800 -0
  304. package/templates/.claude/ukit/index/query-index.mjs +150 -0
  305. package/templates/.claude/ukit/index/refresh-index.mjs +57 -0
  306. package/templates/.claude/ukit/index/reset-auto-permissions.mjs +76 -0
  307. package/templates/.claude/ukit/index/resolve-context.mjs +279 -0
  308. package/templates/.claude/ukit/index/route-catalog.mjs +258 -0
  309. package/templates/.claude/ukit/index/route-task.mjs +1994 -0
  310. package/templates/.claude/ukit/index/triage.mjs +133 -0
  311. package/templates/.claude/ukit/index/verify-context.mjs +689 -0
  312. package/templates/.claude/ukit/runtime/compact-threshold.mjs +1013 -0
  313. package/templates/.claude/ukit/runtime/output-compression.mjs +1340 -0
  314. package/templates/.claude/ukit/runtime/reinject-context.mjs +874 -0
  315. package/templates/.claude/ukit/runtime/token-utils.mjs +500 -0
  316. package/templates/.codex/README.md +83 -0
  317. package/templates/.codex/settings.json +187 -0
  318. package/templates/.gitignore +75 -0
  319. package/templates/AGENTS.md +116 -0
  320. package/templates/CLAUDE.md +93 -0
  321. package/templates/adapter-presets/antigravity/README.md +22 -0
  322. package/templates/adapter-presets/antigravity/rules.md +49 -0
  323. package/templates/adapter-presets/claude/settings.local.json +42 -0
  324. package/templates/adapter-presets/codex/settings.local.json +6 -0
  325. package/templates/adapter-presets/opencode/opencode.template.json +1 -0
  326. package/templates/docs/BUGFIX.md +20 -0
  327. package/templates/docs/BUG_INDEX.md +12 -0
  328. package/templates/docs/BUG_METRICS.md +7 -0
  329. package/templates/docs/BUG_TEMPLATE.md +13 -0
  330. package/templates/docs/CODE_MAP.md +35 -0
  331. package/templates/docs/INSTALL.md +113 -0
  332. package/templates/docs/MEMORY.md +49 -0
  333. package/templates/docs/PROJECT.md +50 -0
  334. package/templates/docs/UKIT_USAGE_GUIDE.md +147 -0
  335. package/templates/docs/WORKLOG.md +10 -0
  336. package/templates/ukit/README.md +14 -0
  337. package/templates/ukit/storage/cache/compact-history.json +3 -0
  338. package/templates/ukit/storage/cache/compact-pressure.json +1 -0
  339. package/templates/ukit/storage/cache/output-history.json +3 -0
  340. package/templates/ukit/storage/cache/prompt-cache.json +3 -0
  341. package/templates/ukit/storage/config.json +37 -0
  342. package/templates/ukit/storage/memory/projects/.gitkeep +2 -0
  343. package/templates/ukit/storage/memory/sessions/.gitkeep +0 -0
  344. package/templates/ukit/storage/memory/user.json +5 -0
@@ -0,0 +1,1506 @@
1
+ # DuraOne Frontend Reference
2
+ ## Vue 3 / Nuxt 3 — Options API Style
3
+
4
+ > **NGUYÊN TẮC CỐT LÕI**: Tận dụng tối đa những gì đã có sẵn trong `components/` và `composables/`.
5
+ > KHÔNG tự tạo input thô — LUÔN dùng Control components. KHÔNG import composables — Nuxt auto-import.
6
+
7
+ ---
8
+
9
+ ## MỤC LỤC
10
+ 1. [Cấu trúc thư mục FE](#1-cấu-trúc-thư-mục-fe)
11
+ 2. [Cấu trúc Vue Component (Options API)](#2-cấu-trúc-vue-component)
12
+ 3. [Naming Conventions](#3-naming-conventions)
13
+ 4. [CONTROL COMPONENTS — Dùng Tối Đa](#4-control-components)
14
+ - [ControlInput](#controlinput)
15
+ - [ControlDropdownlist](#controldropdownlist)
16
+ - [ControlDatetime](#controldatetime)
17
+ - [ControlCheckbox](#controlcheckbox)
18
+ - [ControlCheckButton](#controlcheckbutton)
19
+ - [ControlTag](#controltag)
20
+ - [ControlButton](#controlbutton)
21
+ - [ControlButtonBar](#controlbuttonbar)
22
+ - [ControlButtonFloat](#controlbuttonfloat)
23
+ - [ControlLabel](#controllabel)
24
+ - [ControlBox](#controlbox)
25
+ - [ControlUpload](#controlupload)
26
+ - [ControlMasterBoxColumn](#controlmasterboxcolumn)
27
+ 5. [POPUP COMPONENTS](#5-popup-components)
28
+ - [ControlPopupConfirm](#controlpopupconfirm)
29
+ - [ControlPopupConfirmReject](#controlpopupconfirmreject)
30
+ - [ControlPopupReject](#controlpopupreject)
31
+ - [ControlPopupInfo](#controlpopupinfo)
32
+ - [ControlPopupModalInfo](#controlpopupmodalinfo)
33
+ 6. [GRID COMPONENTS](#6-grid-components)
34
+ - [GridAG](#gridag)
35
+ - [GridAntTable](#gridanttable)
36
+ - [GridHtmlTable](#gridhtmltable)
37
+ 7. [PANEL COMPONENTS](#7-panel-components)
38
+ 8. [COMPOSABLES — Dùng Trực Tiếp (Auto-Import)](#8-composables)
39
+ - [dayjs — DATETIME STANDARD](#dayjs--datetime-standard)
40
+ - [Dropdown Loading Pattern](#dropdown-loading-pattern)
41
+ 9. [API Call Patterns](#9-api-call-patterns)
42
+ 10. [Pattern: Trang Danh Sách](#10-pattern-trang-danh-sách)
43
+ 11. [Pattern: Trang Form](#11-pattern-trang-form)
44
+ 12. [Routing & Navigation](#12-routing--navigation)
45
+ 13. [Validation Pattern](#13-validation-pattern)
46
+ 14. [Đề Xuất Components Mới](#14-đề-xuất-components-mới)
47
+
48
+ ---
49
+
50
+ ## 1. Cấu Trúc Thư Mục FE
51
+
52
+ ### 1.1. Cấu Trúc Gốc Nuxt Project
53
+
54
+ ```
55
+ FE_DuraOne/
56
+ ├── assets/ # Tài nguyên tĩnh (SCSS, fonts, images) — được Nuxt xử lý qua build pipeline
57
+ ├── components/ # Tất cả Vue components — Nuxt auto-import, KHÔNG cần import thủ công
58
+ ├── composables/ # Logic dùng chung — Nuxt auto-import, KHÔNG cần import thủ công
59
+ ├── dist/ # Output build production (do Nuxt generate) — KHÔNG chỉnh sửa thủ công
60
+ ├── docs/ # Tài liệu dự án, hướng dẫn, ghi chú kỹ thuật
61
+ ├── layouts/ # Layout wrappers (default, admin, blank...) — bọc ngoài mỗi page
62
+ ├── middleware/ # Route middleware (auth check, permission guard) — chạy trước khi vào page
63
+ ├── node_modules/ # Dependencies (do yarn install) — KHÔNG chỉnh sửa, KHÔNG commit
64
+ ├── pages/ # Các trang Vue — cấu trúc thư mục = routing tự động của Nuxt
65
+ ├── plugins/ # Nuxt plugins (Ant Design Vue, ECharts, dayjs, v.v.) — chạy khi app khởi tạo
66
+ ├── public/ # File tĩnh phục vụ trực tiếp (favicon, robots.txt) — KHÔNG qua build pipeline
67
+
68
+ ├── .gitignore # Danh sách file/folder git bỏ qua
69
+ ├── AGENTS.md # Hướng dẫn cho AI agents làm việc với project
70
+ ├── api.http # File test API thủ công (dùng với REST Client extension)
71
+ ├── app.vue # Root component của Nuxt app — entry point, chứa <NuxtLayout> + <NuxtPage>
72
+ ├── CLAUDE.md # Hướng dẫn riêng cho Claude AI khi làm việc với project
73
+ ├── nuxt.config.ts # Cấu hình Nuxt (modules, runtime config, CSS, vite options)
74
+ ├── package.json # Dependencies + scripts (dev, build, generate)
75
+ ├── tsconfig.json # Cấu hình TypeScript (Nuxt tự extend)
76
+ └── yarn.lock # Lock file dependencies — KHÔNG chỉnh sửa thủ công
77
+ ```
78
+
79
+ ### 1.2. Chi Tiết Bên Trong `components/` và `composables/`
80
+
81
+ ```
82
+ components/
83
+ │ ├── Control/ # Form controls — LUÔN dùng thay vì raw input
84
+ │ │ ├── Box.vue
85
+ │ │ ├── Button.vue
86
+ │ │ ├── ButtonBar.vue
87
+ │ │ ├── ButtonFloat.vue
88
+ │ │ ├── CheckButton.vue
89
+ │ │ ├── Checkbox.vue
90
+ │ │ ├── Datetime.vue
91
+ │ │ ├── Dropdownlist.vue
92
+ │ │ ├── Input.vue
93
+ │ │ ├── Label.vue
94
+ │ │ ├── Tag.vue
95
+ │ │ ├── Upload.vue
96
+ │ │ ├── Master/BoxColumn.vue
97
+ │ │ └── Popup/
98
+ │ │ ├── Confirm.vue
99
+ │ │ ├── ConfirmReject.vue
100
+ │ │ ├── Info.vue
101
+ │ │ ├── ModalInfo.vue
102
+ │ │ └── Reject.vue
103
+ │ ├── ControlMobile/ # Mobile variants
104
+ │ │ ├── PagingBar.vue
105
+ │ │ └── UploadImage.vue
106
+ │ ├── Grid/
107
+ │ │ ├── AG.vue # Main grid — dùng cho mọi danh sách
108
+ │ │ ├── AntTable.vue
109
+ │ │ └── HtmlTable.vue
110
+ │ ├── Panel/
111
+ │ │ └── FormView.vue # Auto CRUD form
112
+ │ ├── Partial/ # Layout partials (Header, Sidebar, Footer)
113
+ │ └── PDFViewer.vue
114
+
115
+ composables/ # Auto-imported bởi Nuxt — KHÔNG import thủ công
116
+ │ ├── masterApi.js # Domain API functions (get data cho nhiều trang)
117
+ │ ├── useRequest.js # HTTP wrapper + GZIP compression — MỌI API call phải qua đây
118
+ │ ├── useSession.js # localStorage session management
119
+ │ ├── useTranslation.js # i18n — hàm t() để đa ngôn ngữ
120
+ │ ├── useWebSocket.js # WebSocket connection
121
+ │ ├── state.js # Global reactive state (user, loading, language...)
122
+ │ ├── utils.js # Utility functions (format, convert, validate dùng chung)
123
+ │ ├── userObj.js # User-related helpers
124
+ │ └── indexDBStore.js # IndexedDB storage
125
+ ```
126
+
127
+ ---
128
+
129
+ ## 2. Cấu Trúc Vue Component
130
+
131
+ **⚠️ LUÔN dùng Options API. KHÔNG dùng `<script setup>`. KHÔNG import composables.**
132
+
133
+ ```vue
134
+ <template>
135
+ <!-- HTML -->
136
+ </template>
137
+
138
+ <script>
139
+ // ⚠️ KHÔNG import — Nuxt 3 auto-import tất cả từ /composables/
140
+ // state, get_schema, request, formatDate, show_message, t(), get_*... đều sẵn dùng
141
+
142
+ export default {
143
+ props: {
144
+ propName: { type: String, default: "" },
145
+ },
146
+ data() {
147
+ return {
148
+ workingObj: {},
149
+ rows: [],
150
+ // dropdowns
151
+ branchList: [],
152
+ // ui flags
153
+ is_saving: false,
154
+ show_confirm_delete: false,
155
+ };
156
+ },
157
+ computed: {
158
+ is_locked() {
159
+ return !this.workingObj.can_edit;
160
+ },
161
+ },
162
+ watch: {
163
+ "workingObj.id_branch"(newVal) {
164
+ this.on_branch_change(newVal);
165
+ },
166
+ },
167
+ async created() {
168
+ if (!authorize_menu(this.$route.fullPath)) {
169
+ this.$router.push(state.homepage);
170
+ return;
171
+ }
172
+ state.page_header = t("Page Title");
173
+ await this.init();
174
+ },
175
+ methods: {
176
+ async init() {
177
+ await Promise.all([this.get_data(), this.load_dropdowns()]);
178
+ },
179
+ async get_data() { /* ... */ },
180
+ async load_dropdowns() { /* ... */ },
181
+ async save() { /* ... */ },
182
+ validate() { /* ... */ },
183
+ },
184
+ };
185
+ </script>
186
+
187
+ <style scoped></style>
188
+ ```
189
+
190
+ ---
191
+
192
+ ## 3. Naming Conventions
193
+
194
+ | Loại | Convention | Ví dụ |
195
+ |------|-----------|-------|
196
+ | Page files | PascalCase | `AgreementList.vue`, `UserForm.vue` |
197
+ | Page folders | PascalCase | `Agreement/`, `Master/Users/` |
198
+ | Component files | PascalCase | `ControlInput.vue`, `GridAG.vue` |
199
+ | Composables | camelCase | `masterApi.js`, `useRequest.js` |
200
+ | JS variables | camelCase | `workingObj`, `branchList` |
201
+ | DB field keys | snake_case | `id_agreement`, `created_at` |
202
+ | ID fields | `id_` prefix | `id_agreement`, `id_user` |
203
+ | Boolean fields | `can_`/`is_`/`has_`/`show_` | `can_edit`, `is_locked`, `show_confirm` |
204
+ | Data methods | `get_` / `load_` | `get_data()`, `load_dropdowns()` |
205
+ | Action methods | verb | `save()`, `approve()`, `delete_record()` |
206
+ | Event handlers | `on_` | `on_row_click()`, `on_branch_change()` |
207
+
208
+ ---
209
+
210
+ ## 4. CONTROL COMPONENTS
211
+
212
+ > **KHÔNG tự tạo `<input>`, `<select>`, `<button>` raw** — luôn dùng những component dưới đây.
213
+
214
+ ---
215
+
216
+ ### ControlInput
217
+
218
+ Text, number, password, textarea — tất cả đều qua component này.
219
+
220
+ ```vue
221
+ <!-- Text -->
222
+ <ControlInput
223
+ v-model="workingObj.agreement_number"
224
+ name="Agreement Number"
225
+ type="text"
226
+ :required="true"
227
+ :disabled="is_locked"
228
+ placeholder="Enter number..."
229
+ />
230
+
231
+ <!-- Textarea -->
232
+ <ControlInput
233
+ v-model="workingObj.note"
234
+ name="Note"
235
+ type="textarea"
236
+ :rows="4"
237
+ :disabled="is_locked"
238
+ />
239
+
240
+ <!-- Number -->
241
+ <ControlInput
242
+ v-model="workingObj.amount"
243
+ name="Amount"
244
+ type="number"
245
+ :required="true"
246
+ :disabled="is_locked"
247
+ />
248
+
249
+ <!-- Password -->
250
+ <ControlInput
251
+ v-model="form.password"
252
+ name="Password"
253
+ type="password"
254
+ :required="true"
255
+ />
256
+
257
+ <!-- Search (emit onSearch khi nhấn Enter/button) -->
258
+ <ControlInput
259
+ v-model="keyword"
260
+ name=""
261
+ type="search"
262
+ placeholder="Search..."
263
+ @onSearch="get_data"
264
+ />
265
+ ```
266
+
267
+ **Props:** `modelValue`, `name` (label), `type` (text|number|password|textarea|search), `placeholder`, `required`, `disabled`, `rows` (cho textarea), `classes`, `label_classes`, `id`
268
+
269
+ ---
270
+
271
+ ### ControlDropdownlist
272
+
273
+ Select đơn hoặc multiple — có search, có hỗ trợ show_key.
274
+
275
+ ```vue
276
+ <!-- Dropdown đơn -->
277
+ <ControlDropdownlist
278
+ v-model="workingObj.id_branch"
279
+ name="Branch"
280
+ :list="branchList"
281
+ :required="true"
282
+ :disabled="is_locked"
283
+ placeholder="Select branch..."
284
+ />
285
+
286
+ <!-- Multiple select -->
287
+ <ControlDropdownlist
288
+ v-model="workingObj.id_tags"
289
+ name="Tags"
290
+ :list="tagList"
291
+ :multiple="true"
292
+ :disabled="is_locked"
293
+ />
294
+
295
+ <!-- Hiển thị value kèm label -->
296
+ <ControlDropdownlist
297
+ v-model="workingObj.status"
298
+ name="Status"
299
+ :list="statusList"
300
+ :show_key="true"
301
+ />
302
+ ```
303
+
304
+ **Props:** `modelValue`, `name`, `list` (Array `[{value, label}]`), `required`, `disabled`, `multiple`, `placeholder`, `show_key`, `icon`, `classes`, `id`
305
+
306
+ **Lưu ý:** `list` phải có format `{ value: ..., label: ... }` — không đổi key.
307
+
308
+ ---
309
+
310
+ ### ControlDatetime
311
+
312
+ Date picker, month picker, hoặc year picker.
313
+
314
+ ```vue
315
+ <!-- Date -->
316
+ <ControlDatetime
317
+ v-model="workingObj.start_date"
318
+ name="Start Date"
319
+ type="date"
320
+ :required="true"
321
+ :disabled="is_locked"
322
+ :maxDate="workingObj.end_date"
323
+ />
324
+
325
+ <!-- Date + Time -->
326
+ <ControlDatetime
327
+ v-model="workingObj.appointment_time"
328
+ name="Appointment"
329
+ type="date"
330
+ :show_time="true"
331
+ :required="true"
332
+ />
333
+
334
+ <!-- Month picker -->
335
+ <ControlDatetime
336
+ v-model="filter_month"
337
+ name="Month"
338
+ type="month"
339
+ />
340
+
341
+ <!-- Year picker -->
342
+ <ControlDatetime
343
+ v-model="filter_year"
344
+ name="Year"
345
+ type="year"
346
+ />
347
+ ```
348
+
349
+ **Props:** `modelValue`, `name`, `type` (date|month|year), `show_time`, `required`, `disabled`, `minDate`, `maxDate`, `classes`, `id`
350
+
351
+ **Emit:** `update:modelValue` với format `YYYY-MM-DD`
352
+
353
+ ---
354
+
355
+ ### ControlCheckbox
356
+
357
+ Boolean checkbox với label.
358
+
359
+ ```vue
360
+ <ControlCheckbox
361
+ v-model="workingObj.is_active"
362
+ label="Active"
363
+ :disabled="is_locked"
364
+ />
365
+
366
+ <ControlCheckbox
367
+ v-model="workingObj.has_vat"
368
+ label="Include VAT"
369
+ />
370
+ ```
371
+
372
+ **Props:** `modelValue`, `label`, `disabled`
373
+
374
+ ---
375
+
376
+ ### ControlCheckButton
377
+
378
+ Checkbox styled như button — dùng khi cần toggle nổi bật hơn.
379
+
380
+ ```vue
381
+ <ControlCheckButton
382
+ v-model="filter_is_urgent"
383
+ label="Urgent Only"
384
+ />
385
+
386
+ <ControlCheckButton
387
+ v-model="workingObj.is_adhoc"
388
+ label="Ad-hoc"
389
+ :disabled="is_locked"
390
+ />
391
+ ```
392
+
393
+ **Props:** `modelValue`, `label`, `disabled`, `id`
394
+
395
+ ---
396
+
397
+ ### ControlTag
398
+
399
+ Multi-tag input — nhập nhiều giá trị tự do.
400
+
401
+ ```vue
402
+ <ControlTag
403
+ v-model="workingObj.keywords"
404
+ name="Keywords"
405
+ placeholder="Add keyword, press Enter..."
406
+ :disabled="is_locked"
407
+ />
408
+ ```
409
+
410
+ **Props:** `modelValue` (Array), `name`, `placeholder`, `required`, `disabled`, `classes`, `label_classes`, `id`
411
+
412
+ ---
413
+
414
+ ### ControlButton
415
+
416
+ Button chính — có loading state tự động (1 giây sau click).
417
+
418
+ ```vue
419
+ <!-- Primary -->
420
+ <ControlButton name="Save" type="primary" @onClick="save" />
421
+
422
+ <!-- Danger -->
423
+ <ControlButton name="Delete" type="danger" @onClick="show_confirm_delete = true" />
424
+
425
+ <!-- Secondary -->
426
+ <ControlButton name="Back" type="secondary" @onClick="go_back" />
427
+
428
+ <!-- Success -->
429
+ <ControlButton name="Approve" type="success" @onClick="approve" />
430
+
431
+ <!-- Custom color -->
432
+ <ControlButton name="Export" color="#6c5ce7" @onClick="export_data" />
433
+ ```
434
+
435
+ **Props:** `name` (label), `type` (primary|secondary|danger|success|warning), `color` (custom hex), `classes`
436
+
437
+ **Emit:** `onClick`
438
+
439
+ ---
440
+
441
+ ### ControlButtonBar
442
+
443
+ Group nhiều button liền nhau.
444
+
445
+ ```vue
446
+ <ControlButtonBar
447
+ :button_list="[
448
+ { name: 'Close', type: 'secondary' },
449
+ { name: 'Delete', type: 'danger' },
450
+ { name: 'Save', type: 'success' },
451
+ ]"
452
+ @clickClose="close"
453
+ @clickDelete="show_confirm_delete = true"
454
+ @clickSave="save"
455
+ />
456
+ ```
457
+
458
+ **Props:** `button_list` (Array `[{name, type}]`)
459
+
460
+ **Emit:** `click{ButtonName}` — dynamic theo tên button (e.g. `clickSave`, `clickDelete`)
461
+
462
+ ---
463
+
464
+ ### ControlButtonFloat
465
+
466
+ Floating action button cố định góc dưới phải — dùng cho trang danh sách để "Add New".
467
+
468
+ ```vue
469
+ <ControlButtonFloat name="Add New" @clickButtonFloat="go_to_form" />
470
+ ```
471
+
472
+ **Props:** `name`
473
+
474
+ **Emit:** `clickButtonFloat`
475
+
476
+ ---
477
+
478
+ ### ControlLabel
479
+
480
+ Label riêng — dùng khi cần label mà không kèm input.
481
+
482
+ ```vue
483
+ <ControlLabel name="Section Title" :required="false" />
484
+ <ControlLabel name="Email" icon="fas fa-envelope" :required="true" />
485
+ ```
486
+
487
+ **Props:** `name`, `required`, `icon` (FontAwesome class), `classes`
488
+
489
+ ---
490
+
491
+ ### ControlBox
492
+
493
+ Container có header + màu sắc theo loại — dùng để group fields.
494
+
495
+ ```vue
496
+ <!-- Info box -->
497
+ <ControlBox title="Agreement Info" type="info" :col="12">
498
+ <div class="row">
499
+ <div class="col-md-6">
500
+ <ControlInput v-model="workingObj.agreement_number" name="Number" />
501
+ </div>
502
+ </div>
503
+ </ControlBox>
504
+
505
+ <!-- Success status box -->
506
+ <ControlBox title="Approval" type="success" :col="6">
507
+ <ControlLabel :name="workingObj.approved_by" />
508
+ </ControlBox>
509
+
510
+ <!-- Warning box -->
511
+ <ControlBox title="Attention" type="warning" :col="12">
512
+ <p>Please review before submitting.</p>
513
+ </ControlBox>
514
+ ```
515
+
516
+ **Props:** `title`, `type` (info|success|danger|warning), `col` (3|4|6|12), `show_icon`
517
+
518
+ ---
519
+
520
+ ### ControlUpload
521
+
522
+ File upload với drag-and-drop, tối đa 3 file, 10MB/file.
523
+
524
+ ```vue
525
+ <ControlUpload
526
+ v-model="fileList"
527
+ label="Click or drag file to upload"
528
+ hint="Max 3 files, 10MB each"
529
+ @uploadFinish="on_upload_finish"
530
+ />
531
+ ```
532
+
533
+ ```javascript
534
+ methods: {
535
+ on_upload_finish(file) {
536
+ this.workingObj.file_url = file.url || file.name;
537
+ }
538
+ }
539
+ ```
540
+
541
+ **Props:** `modelValue` (Array), `label`, `hint`, `required`
542
+
543
+ **Emit:** `uploadFinish(file)`
544
+
545
+ ---
546
+
547
+ ### ControlMasterBoxColumn
548
+
549
+ Layout wrapper tự điều chỉnh col width theo số item — dùng trong PanelFormView.
550
+
551
+ ```vue
552
+ <!-- item.position.length = 1 → col-md-12 -->
553
+ <!-- item.position.length = 2 → col-md-6 -->
554
+ <!-- item.position.length = 3 → col-md-4 -->
555
+ <!-- item.position.length = 4 → col-md-3 -->
556
+ <ControlMasterBoxColumn :item="field">
557
+ <ControlInput v-model="obj[field.key]" :name="field.title" />
558
+ </ControlMasterBoxColumn>
559
+ ```
560
+
561
+ ---
562
+
563
+ ## 5. POPUP COMPONENTS
564
+
565
+ ---
566
+
567
+ ### ControlPopupConfirm
568
+
569
+ Confirm dialog đơn giản — Yes/No.
570
+
571
+ ```vue
572
+ <ControlPopupConfirm
573
+ :visible="show_confirm_delete"
574
+ title="Confirm Delete"
575
+ message="Are you sure you want to delete this record?"
576
+ @onConfirm="on_confirm_delete"
577
+ @onClose="show_confirm_delete = false"
578
+ />
579
+ ```
580
+
581
+ ```javascript
582
+ data() {
583
+ return { show_confirm_delete: false };
584
+ },
585
+ methods: {
586
+ async on_confirm_delete() {
587
+ this.show_confirm_delete = false;
588
+ await request("/delete", { table: "agreement", id_agreement: this.workingObj.id_agreement });
589
+ show_message("success", t("Deleted successfully"));
590
+ this.$router.push("/Agreement/AgreementList");
591
+ }
592
+ }
593
+ ```
594
+
595
+ **Props:** `visible`, `title`, `message` (HTML supported)
596
+
597
+ **Emits:** `onConfirm`, `onClose`
598
+
599
+ ---
600
+
601
+ ### ControlPopupConfirmReject
602
+
603
+ Popup confirm có checkbox Adhoc/OnPlan + textarea lý do — dùng cho approval flow.
604
+
605
+ ```vue
606
+ <ControlPopupConfirmReject
607
+ :visible="show_confirm_approve"
608
+ title="Classify Agreement"
609
+ message="Please classify this agreement type"
610
+ @onConfirm="on_confirm_approve"
611
+ @onClose="show_confirm_approve = false"
612
+ />
613
+ ```
614
+
615
+ ```javascript
616
+ // onConfirm nhận (reason, is_adhoc, is_onplan)
617
+ async on_confirm_approve(reason, is_adhoc, is_onplan) {
618
+ await request("/save", {
619
+ table: "agreement",
620
+ key_array: ["id_agreement"],
621
+ id_agreement: this.workingObj.id_agreement,
622
+ status: "approved",
623
+ approve_reason: reason,
624
+ is_adhoc,
625
+ is_onplan,
626
+ });
627
+ }
628
+ ```
629
+
630
+ **Props:** `visible`, `title`, `message`
631
+
632
+ **Emits:** `onConfirm(reason, is_adhoc, is_onplan)`, `onClose`
633
+
634
+ ---
635
+
636
+ ### ControlPopupReject
637
+
638
+ Popup từ chối có Adhoc/OnPlan checkbox + lý do — dùng cho rejection flow.
639
+
640
+ ```vue
641
+ <ControlPopupReject
642
+ :visible="show_reject"
643
+ title="Reject Agreement"
644
+ message="Please provide rejection reason"
645
+ @onConfirm="on_reject"
646
+ @onClose="show_reject = false"
647
+ />
648
+ ```
649
+
650
+ ```javascript
651
+ async on_reject(reason, is_adhoc, is_onplan) {
652
+ await request("/save", {
653
+ table: "agreement",
654
+ key_array: ["id_agreement"],
655
+ id_agreement: this.workingObj.id_agreement,
656
+ status: "rejected",
657
+ reject_reason: reason,
658
+ });
659
+ }
660
+ ```
661
+
662
+ **Props:** `visible`, `title`, `message`
663
+
664
+ **Emits:** `onConfirm(reason, is_adhoc, is_onplan)`, `onClose`
665
+
666
+ ---
667
+
668
+ ### ControlPopupInfo
669
+
670
+ Info modal hiển thị kết quả thao tác.
671
+
672
+ ```vue
673
+ <ControlPopupInfo
674
+ :visible="show_result"
675
+ status="success"
676
+ title="Saved Successfully"
677
+ />
678
+ ```
679
+
680
+ **Props:** `visible`, `status` (success|error|warning|info), `title`
681
+
682
+ ---
683
+
684
+ ### ControlPopupModalInfo
685
+
686
+ Modal chứa GridAG — hiển thị danh sách chi tiết trong popup.
687
+
688
+ ```vue
689
+ <ControlPopupModalInfo
690
+ :visible="show_detail_popup"
691
+ title="Transaction History"
692
+ :rows="transaction_rows"
693
+ :columns="transaction_columns"
694
+ />
695
+ ```
696
+
697
+ **Props:** `visible`, `title`, `rows`, `columns`
698
+
699
+ ---
700
+
701
+ ## 6. GRID COMPONENTS
702
+
703
+ ---
704
+
705
+ ### GridAG
706
+
707
+ **Component chính cho mọi danh sách dữ liệu** — AG Grid với đầy đủ tính năng.
708
+
709
+ ```vue
710
+ <GridAG
711
+ :rows="rows"
712
+ :columns="columns"
713
+ :can_add="true"
714
+ :can_delete="false"
715
+ :can_export="true"
716
+ :can_search="true"
717
+ :can_refresh="true"
718
+ height="500px"
719
+ @onAdd="go_to_new_form"
720
+ @onClickDetail="go_to_edit_form"
721
+ @reloadData="get_data"
722
+ />
723
+ ```
724
+
725
+ **Column Definition:**
726
+ ```javascript
727
+ columns: [
728
+ // Text
729
+ { key: "request_no", title: t("Request No"), type: "text", width: 150 },
730
+ // Number (auto format)
731
+ { key: "amount", title: t("Amount"), type: "number", width: 120 },
732
+ // Date (auto format DD/MM/YYYY)
733
+ { key: "created_at", title: t("Date"), type: "date", width: 110 },
734
+ // Date + Time
735
+ { key: "updated_at", title: t("Updated"), type: "datetime", width: 140 },
736
+ // HTML render
737
+ { key: "status_html", title: t("Status"), type: "html", width: 100 },
738
+ // Checkbox (read-only)
739
+ { key: "is_active", title: t("Active"), type: "checkbox", width: 80 },
740
+ // Editable dropdown
741
+ {
742
+ key: "status",
743
+ title: t("Status"),
744
+ type: "dropdown",
745
+ can_edit: true,
746
+ dropdown_list: [
747
+ { value: "draft", label: "Draft" },
748
+ { value: "approved", label: "Approved" },
749
+ ],
750
+ width: 120,
751
+ },
752
+ // Sum aggregation
753
+ { key: "amount", title: t("Amount"), type: "number", aggFunc: "sum", width: 120 },
754
+ ],
755
+ ```
756
+
757
+ **Props đầy đủ:**
758
+ - `rows` (Array) — data
759
+ - `columns` (Array) — column defs
760
+ - `can_add` (Boolean, default: true)
761
+ - `can_search` (Boolean, default: true)
762
+ - `can_edit` (Boolean, default: true) — hiện nút Edit/Detail
763
+ - `can_delete` (Boolean, default: false)
764
+ - `can_export` (Boolean, default: true)
765
+ - `can_refresh` (Boolean, default: true)
766
+ - `can_sync` (Boolean, default: false) — sync từ Excel
767
+ - `can_upload` (Boolean, default: false)
768
+ - `have_checkbox` (Boolean, default: false)
769
+ - `show_total` (Boolean, default: false) — pinned bottom row
770
+ - `height` (String, default: "600px")
771
+ - `is_fix_height` (Boolean, default: false)
772
+ - `is_loading` (Boolean, default: false)
773
+ - `table` (String) — table name cho sync
774
+ - `table_schema` (String)
775
+ - `detail_button_list` (Array) — extra per-row buttons
776
+ - `extra_button` (Array) — extra toolbar buttons
777
+
778
+ **Emits:**
779
+ - `onAdd` — Add button
780
+ - `onClickDetail(row)` — Edit/Detail button
781
+ - `onExtraDetailBtnClick(key, row)` — extra row button
782
+ - `onCellValueChanged(event)` — inline edit
783
+ - `reloadData()` — refresh
784
+ - `getSelectedRows(key, rows)` — checkbox selection
785
+ - `onDeleteRowIndex(index)` — delete row
786
+ - `onExtraBtnClick(key)` — extra toolbar button
787
+
788
+ ---
789
+
790
+ ### GridAntTable
791
+
792
+ Table đơn giản hơn GridAG — dùng khi cần pagination nhẹ.
793
+
794
+ ```vue
795
+ <GridAntTable
796
+ :rows="rows"
797
+ :columns="columns"
798
+ :is_checkbox="false"
799
+ :is_edit="true"
800
+ :is_export="true"
801
+ @onClickDetail="on_click_detail"
802
+ />
803
+ ```
804
+
805
+ **Props:** `rows`, `columns` (`[{title, dataIndex}]`), `is_checkbox`, `is_edit`, `is_export`
806
+
807
+ **Emits:** `onClickDetail(row)`, `onClickEdit(row)`
808
+
809
+ ---
810
+
811
+ ### GridHtmlTable
812
+
813
+ Table HTML thuần — dùng cho print hoặc display đơn giản nhất.
814
+
815
+ ```vue
816
+ <GridHtmlTable
817
+ :rows="rows"
818
+ :columns="[
819
+ { caption: 'Name', field: 'full_name' },
820
+ { caption: 'Amount', field: 'amount' },
821
+ ]"
822
+ />
823
+ ```
824
+
825
+ ---
826
+
827
+ ## 7. PANEL COMPONENTS
828
+
829
+ ### PanelFormView
830
+
831
+ Form CRUD tự động — dùng cho Master Data (không cần business logic phức tạp).
832
+
833
+ ```vue
834
+ <PanelFormView
835
+ :workingObj="currentRecord"
836
+ :rows="allRows"
837
+ :columns="formColumns"
838
+ table="branch"
839
+ table_schema="qas"
840
+ :allow_edit="true"
841
+ @reloadData="get_data"
842
+ />
843
+ ```
844
+
845
+ **Column definition cho FormView:**
846
+ ```javascript
847
+ formColumns: [
848
+ {
849
+ key: "id_branch",
850
+ title: "Branch ID",
851
+ type: "auto_generate",
852
+ primary: true, // PK — disable khi update
853
+ show_form: false, // ẩn khỏi form
854
+ },
855
+ {
856
+ key: "branch_name",
857
+ title: "Branch Name",
858
+ type: "input",
859
+ required: true,
860
+ show_form: true,
861
+ position: ["col1"], // position.length = 1 → col-12
862
+ },
863
+ {
864
+ key: "id_region",
865
+ title: "Region",
866
+ type: "dropdown",
867
+ required: true,
868
+ show_form: true,
869
+ position: ["col1", "col2"], // length = 2 → col-6
870
+ dropdown_info: {
871
+ schema: "qas",
872
+ table: "region",
873
+ field: { label: ["region_name"], value: ["id_region"] },
874
+ },
875
+ },
876
+ {
877
+ key: "id_country",
878
+ title: "Country",
879
+ type: "dropdown",
880
+ show_form: true,
881
+ position: ["col1", "col2"],
882
+ dropdown_info: {
883
+ schema: "qas",
884
+ table: "country",
885
+ field: { label: ["country_name"], value: ["id_country"] },
886
+ conditions: { id_region: "id_region" }, // dependent dropdown
887
+ },
888
+ },
889
+ ],
890
+ ```
891
+
892
+ ---
893
+
894
+ ## 8. COMPOSABLES
895
+
896
+ > ⚠️ **KHÔNG import** — Nuxt 3 auto-import tất cả từ `/composables/`. Dùng trực tiếp.
897
+
898
+ ### useRequest.js
899
+
900
+ > 🚫🚫🚫 **CẤM TUYỆT ĐỐI GỌI API RAW** 🚫🚫🚫
901
+ >
902
+ > **KHÔNG BAO GIỜ** dùng `fetch()`, `axios()`, `axios.get()`, `axios.post()`, `axios.put()`, `axios.delete()` hay bất kỳ HTTP client nào khác.
903
+ >
904
+ > MỌI request đến server **BẮT BUỘC** phải đi qua 3 hàm duy nhất bên dưới (đã auto-import từ `composables/useRequest.js`):
905
+ > - `request(url, data, method)` — API chuẩn, có GZIP + loading state
906
+ > - `requestForm(url, formData)` — Upload file (multipart/form-data)
907
+ > - `request_origin(url, data)` — Custom endpoint (không qua `/api/select`)
908
+ >
909
+ > **Nếu code sinh ra chứa `fetch(`, `axios(`, `axios.get(`, `axios.post(` → CODE SAI, PHẢI SỬA LẠI NGAY.**
910
+ > Không có ngoại lệ — kể cả "chỉ test nhanh", "gọi API bên ngoài", hay "chỉ 1 lần".
911
+
912
+ ```javascript
913
+ // GET data (qua generic /api/select)
914
+ const rows = await request("/select", {
915
+ schema: get_schema(),
916
+ table: "v_agreement",
917
+ conditions: JSON.stringify({ id_branch: 5, status: "active" }),
918
+ order_by: ["created_at desc"],
919
+ columns: "*", // hoặc ["col1", "col2"]
920
+ }, "get");
921
+
922
+ // POST save (insert id='' hoặc update id=uuid)
923
+ await request("/save", {
924
+ table: "agreement",
925
+ key_array: ["id_agreement"],
926
+ id_agreement: "", // '' = insert, uuid = update
927
+ agreement_number: "AGR-001",
928
+ amount: 50000,
929
+ });
930
+
931
+ // Custom endpoint
932
+ await request_origin("/api/agreement/approve", {
933
+ id_agreement: "uuid-here",
934
+ schema: get_schema(),
935
+ });
936
+
937
+ // Upload file
938
+ const formData = new FormData();
939
+ formData.append("file", fileObj);
940
+ formData.append("folder", "agreements");
941
+ await requestForm("/api/upload_to_cloud", formData);
942
+ ```
943
+
944
+ ### Dropdown Loading Pattern
945
+
946
+ Dùng pattern này để load dropdown list. Gọi trong `load_dropdowns()` hoặc `init()`.
947
+
948
+ ```javascript
949
+ // Pattern chuẩn:
950
+ // 1. Dùng separate `let data = {}` object (không inline)
951
+ // 2. Check resp.length > 0 trước khi gán
952
+ // 3. Dùng convertToDropdownValue() để chuyển sang {value, label}
953
+
954
+ async getAreaList() {
955
+ let data = {
956
+ schema: get_schema(),
957
+ table: "mkt_stock_area",
958
+ };
959
+ let resp = await request("/select", data, "get");
960
+ if (resp.length > 0) {
961
+ this.areaList = convertToDropdownValue(resp, "name", "name");
962
+ }
963
+ },
964
+
965
+ async getBranchList() {
966
+ let data = {
967
+ schema: get_schema(),
968
+ table: "branch",
969
+ conditions: JSON.stringify({ is_active: true }),
970
+ order_by: ["branch_name asc"],
971
+ };
972
+ let resp = await request("/select", data, "get");
973
+ if (resp.length > 0) {
974
+ this.branchList = convertToDropdownValue(resp, "id_branch", "branch_name");
975
+ }
976
+ },
977
+ ```
978
+
979
+ **Gọi đồng thời trong `load_dropdowns()`:**
980
+ ```javascript
981
+ async load_dropdowns() {
982
+ await Promise.all([
983
+ this.getAreaList(),
984
+ this.getBranchList(),
985
+ ]);
986
+ },
987
+ ```
988
+
989
+ **`convertToDropdownValue(arr, valueField, labelField)`** — chuyển array thô thành `[{value, label}]`:
990
+ ```javascript
991
+ // Khi value và label dùng cùng field (enum-style)
992
+ convertToDropdownValue(resp, "name", "name") // → [{ value: "Zone A", label: "Zone A" }]
993
+
994
+ // Khi value và label khác field
995
+ convertToDropdownValue(resp, "id_branch", "branch_name") // → [{ value: "uuid...", label: "Main Branch" }]
996
+ ```
997
+
998
+ ---
999
+
1000
+ ### masterApi.js — Pattern
1001
+
1002
+ ```javascript
1003
+ // Get single record — trả về {} nếu không có
1004
+ export const get_agreement_by_id = async (id_agreement) => {
1005
+ const result = await request("/select", {
1006
+ schema: get_schema(),
1007
+ table: "v_agreement",
1008
+ conditions: JSON.stringify({ id_agreement }),
1009
+ }, "get");
1010
+ return result.length > 0 ? result[0] : {};
1011
+ };
1012
+
1013
+ // Get dropdown list — trả về [{value, label}]
1014
+ export const get_branch_list_dropdown = async () => {
1015
+ return await request("/select", {
1016
+ schema: get_schema(),
1017
+ table: "branch",
1018
+ columns: ["id_branch as value", "branch_name as label"],
1019
+ conditions: JSON.stringify({ is_active: true }),
1020
+ order_by: ["branch_name asc"],
1021
+ }, "get");
1022
+ };
1023
+ ```
1024
+
1025
+ ### state.js
1026
+
1027
+ ```javascript
1028
+ state.page_header = t("Agreement Management"); // set page title
1029
+ state.username // current user
1030
+ state.usergroup // user group
1031
+ state.language // current language
1032
+ state.loading // boolean — auto-managed by request()
1033
+ state.loading_count // internal counter
1034
+
1035
+ get_schema() // → "qas" | "prd"
1036
+ baseurl() // → base URL
1037
+ authorize_menu(path) // → boolean
1038
+ menu() // → menu structure
1039
+ ```
1040
+
1041
+ ### utils.js
1042
+
1043
+ ```javascript
1044
+ formatDate(date, "YYYY-MM-DD") // "2024-01-15"
1045
+ formatDate(date, "DD/MM/YYYY") // "15/01/2024"
1046
+ formatDate(date, "DD/MM/YYYY HH:mm") // "15/01/2024 10:30"
1047
+ formatNumber(1234567.89, ".", 2) // "1.234.567,89"
1048
+ check_is_null_or_blank(value) // true nếu null/undefined/""/[]/{}
1049
+ // → dùng cho MỌI loại: string, array, object
1050
+ // check_is_null_or_blank("") → true
1051
+ // check_is_null_or_blank([]) → true ← array rỗng
1052
+ // check_is_null_or_blank({}) → true ← object rỗng
1053
+ // check_is_null_or_blank(null) → true
1054
+ // check_is_null_or_blank("abc") → false
1055
+ // check_is_null_or_blank([1]) → false
1056
+ check_is_zero(value) // true if 0/null/""
1057
+ convertToDropdownValue(arr, "id", "name") // [{value, label}]
1058
+ show_message("success", "Saved!")
1059
+ show_message("error", "Failed!")
1060
+ show_message("warning", "Check input!")
1061
+ go_to_page("/path", { id: "uuid" })
1062
+ go_back("/fallback")
1063
+ screen_height() // viewport height
1064
+ bodautiengviet(str) // remove Vietnamese diacritics
1065
+ ```
1066
+
1067
+ ### useSession.js
1068
+
1069
+ ```javascript
1070
+ setSession("user_data", response.data, 86400000) // expire 24h
1071
+ const userData = getSession("user_data")
1072
+ clearSession()
1073
+ refreshSession(86400000)
1074
+ ```
1075
+
1076
+ ### useTranslation.js
1077
+
1078
+ ```javascript
1079
+ t("Field Name") // → translated string
1080
+ t("Save") // → "บันทึก" | "Lưu" | "Save"
1081
+ ```
1082
+
1083
+ > **Lưu ý dùng `t()` trong `data()`:** OK vì Nuxt 3 auto-import composables trước khi `data()` chạy.
1084
+ > Dùng bình thường cho column definitions, label mặc định, v.v.
1085
+
1086
+ ### dayjs — DATETIME STANDARD
1087
+
1088
+ > **QUY TẮC BẮT BUỘC**: Mọi biến liên quan đến datetime PHẢI dùng `dayjs()`.
1089
+ > KHÔNG dùng `new Date()`, `Date.now()`, `.getFullYear()`, `.getMonth()`, v.v.
1090
+ > `dayjs` có sẵn qua composables (auto-import) — dùng trực tiếp, không cần import.
1091
+
1092
+ ```javascript
1093
+ // ── Tạo giá trị mặc định cho filter/date fields ──────────────────
1094
+ dayjs().format("YYYY-MM-DD") // "2024-03-26"
1095
+ dayjs().format("YYYY-MM-DD HH:mm:ss") // "2024-03-26 10:30:00"
1096
+ dayjs().startOf("month").format("YYYY-MM-DD") // "2024-03-01"
1097
+ dayjs().endOf("month").format("YYYY-MM-DD") // "2024-03-31"
1098
+ dayjs().subtract(1, "month").startOf("month").format("YYYY-MM-DD") // đầu tháng trước
1099
+ dayjs().subtract(1, "month").endOf("month").format("YYYY-MM-DD") // cuối tháng trước
1100
+
1101
+ // ── Lấy thành phần từ ngày ────────────────────────────────────────
1102
+ dayjs().year() // 2024 ← THAY new Date().getFullYear()
1103
+ dayjs().month() + 1 // 3 ← month() trả về 0-indexed
1104
+ dayjs().date() // 26
1105
+
1106
+ // ── Tính toán, so sánh ───────────────────────────────────────────
1107
+ dayjs("2024-01-15").isAfter(dayjs("2024-01-01")) // true
1108
+ dayjs("2024-01-15").isBefore(dayjs("2024-12-31")) // true
1109
+ dayjs("2024-01-15").diff(dayjs("2024-01-01"), "day") // 14
1110
+ dayjs().add(7, "day").format("YYYY-MM-DD") // 7 ngày sau
1111
+
1112
+ // ── Parse và format ──────────────────────────────────────────────
1113
+ dayjs("2024-01-15 10:30:00").format("DD/MM/YYYY") // "15/01/2024"
1114
+ dayjs("2024-01-15").isValid() // true — check hợp lệ trước dùng
1115
+ ```
1116
+
1117
+ **Pattern trong `data()` — dùng dayjs cho mọi default date:**
1118
+ ```javascript
1119
+ data() {
1120
+ return {
1121
+ from_date: dayjs().startOf("month").format("YYYY-MM-DD"),
1122
+ to_date: dayjs().endOf("month").format("YYYY-MM-DD"),
1123
+ filter_year: dayjs().year(),
1124
+ filter_month: dayjs().format("YYYY-MM"),
1125
+ };
1126
+ },
1127
+ ```
1128
+
1129
+ ---
1130
+
1131
+ ## 9. API Call Patterns
1132
+
1133
+ ### Đọc danh sách (dùng view)
1134
+ ```javascript
1135
+ async get_data() {
1136
+ this.rows = await request("/select", {
1137
+ schema: get_schema(),
1138
+ table: "v_agreement", // LUÔN dùng view, không phải table thô
1139
+ conditions: JSON.stringify({
1140
+ created_at_from: this.from_date,
1141
+ created_at_to: this.to_date,
1142
+ status: this.filter_status || undefined,
1143
+ }),
1144
+ order_by: ["created_at desc"],
1145
+ }, "get");
1146
+ },
1147
+ ```
1148
+
1149
+ ### Lưu (insert/update)
1150
+ ```javascript
1151
+ async save() {
1152
+ if (!this.validate()) return;
1153
+ this.is_saving = true;
1154
+ await request("/save", {
1155
+ table: "agreement",
1156
+ key_array: ["id_agreement"],
1157
+ ...this.workingObj,
1158
+ });
1159
+ this.is_saving = false;
1160
+ show_message("success", t("Saved successfully"));
1161
+ },
1162
+ ```
1163
+
1164
+ ### Xóa mềm (soft delete — preferred)
1165
+ ```javascript
1166
+ async delete_record() {
1167
+ await request("/save", {
1168
+ table: "agreement",
1169
+ key_array: ["id_agreement"],
1170
+ id_agreement: this.workingObj.id_agreement,
1171
+ is_deleted: true,
1172
+ });
1173
+ show_message("success", t("Deleted"));
1174
+ this.$router.push("/Agreement/AgreementList");
1175
+ },
1176
+ ```
1177
+
1178
+ ### Chạy Stored Procedure
1179
+ ```javascript
1180
+ await request("/run_proc", {
1181
+ schema: get_schema(),
1182
+ proc_name: "sp_generate_running_no",
1183
+ params: { prefix: "AGR", year: dayjs().year() }, // ✅ dayjs, KHÔNG dùng new Date()
1184
+ });
1185
+ ```
1186
+
1187
+ ---
1188
+
1189
+ ## 10. Pattern: Trang Danh Sách
1190
+
1191
+ ```vue
1192
+ <template>
1193
+ <div>
1194
+ <!-- Filter -->
1195
+ <div class="row mb-3">
1196
+ <div class="col-md-3">
1197
+ <ControlDatetime v-model="from_date" name="From" type="date" />
1198
+ </div>
1199
+ <div class="col-md-3">
1200
+ <ControlDatetime v-model="to_date" name="To" type="date" />
1201
+ </div>
1202
+ <div class="col-md-3">
1203
+ <ControlDropdownlist v-model="filter_status" name="Status" :list="statusList" placeholder="All" />
1204
+ </div>
1205
+ <div class="col-md-3 d-flex align-items-end gap-2">
1206
+ <ControlButton name="Search" type="primary" @onClick="get_data" />
1207
+ </div>
1208
+ </div>
1209
+
1210
+ <!-- Grid -->
1211
+ <GridAG
1212
+ :rows="rows"
1213
+ :columns="columns"
1214
+ :can_add="true"
1215
+ :can_export="true"
1216
+ @onAdd="() => $router.push('/Agreement/AgreementForm')"
1217
+ @onClickDetail="(row) => $router.push({ path: '/Agreement/AgreementForm', query: { id: row.id_agreement } })"
1218
+ @reloadData="get_data"
1219
+ />
1220
+
1221
+ <!-- Floating add button (mobile/alternative) -->
1222
+ <ControlButtonFloat name="New" @clickButtonFloat="() => $router.push('/Agreement/AgreementForm')" />
1223
+ </div>
1224
+ </template>
1225
+
1226
+ <script>
1227
+ export default {
1228
+ data() {
1229
+ return {
1230
+ from_date: dayjs().subtract(1, "month").startOf("month").format("YYYY-MM-DD"),
1231
+ to_date: dayjs().endOf("month").format("YYYY-MM-DD"),
1232
+ filter_status: "",
1233
+ statusList: [
1234
+ { value: "draft", label: "Draft" },
1235
+ { value: "approved", label: "Approved" },
1236
+ { value: "rejected", label: "Rejected" },
1237
+ ],
1238
+ rows: [],
1239
+ columns: [
1240
+ { key: "request_no", title: t("No."), type: "text", width: 150 },
1241
+ { key: "created_at", title: t("Date"), type: "date", width: 110 },
1242
+ { key: "branch_name",title: t("Branch"), type: "text", width: 150 },
1243
+ { key: "amount", title: t("Amount"), type: "number", width: 120, aggFunc: "sum" },
1244
+ { key: "status", title: t("Status"), type: "text", width: 100 },
1245
+ ],
1246
+ };
1247
+ },
1248
+ async created() {
1249
+ if (!authorize_menu(this.$route.fullPath)) { this.$router.push(state.homepage); return; }
1250
+ state.page_header = t("Agreement List");
1251
+ await this.get_data();
1252
+ },
1253
+ methods: {
1254
+ async get_data() {
1255
+ this.rows = await request("/select", {
1256
+ schema: get_schema(),
1257
+ table: "v_agreement",
1258
+ conditions: JSON.stringify({
1259
+ from_date: this.from_date,
1260
+ to_date: this.to_date,
1261
+ status: this.filter_status || undefined,
1262
+ }),
1263
+ order_by: ["created_at desc"],
1264
+ }, "get");
1265
+ },
1266
+ },
1267
+ };
1268
+ </script>
1269
+ ```
1270
+
1271
+ ---
1272
+
1273
+ ## 11. Pattern: Trang Form
1274
+
1275
+ ```vue
1276
+ <template>
1277
+ <div>
1278
+ <ControlBox title="Agreement Information" type="info" :col="12">
1279
+ <div class="row">
1280
+ <div class="col-md-6">
1281
+ <ControlInput v-model="workingObj.agreement_number" name="Agreement Number"
1282
+ :required="true" :disabled="is_locked" />
1283
+ </div>
1284
+ <div class="col-md-6">
1285
+ <ControlDropdownlist v-model="workingObj.id_branch" name="Branch"
1286
+ :list="branchList" :required="true" :disabled="is_locked" />
1287
+ </div>
1288
+ <div class="col-md-4">
1289
+ <ControlDatetime v-model="workingObj.start_date" name="Start Date"
1290
+ type="date" :required="true" :disabled="is_locked" />
1291
+ </div>
1292
+ <div class="col-md-4">
1293
+ <ControlDatetime v-model="workingObj.end_date" name="End Date"
1294
+ type="date" :required="true" :disabled="is_locked"
1295
+ :minDate="workingObj.start_date" />
1296
+ </div>
1297
+ <div class="col-md-4">
1298
+ <ControlInput v-model="workingObj.amount" name="Amount"
1299
+ type="number" :required="true" :disabled="is_locked" />
1300
+ </div>
1301
+ <div class="col-md-12">
1302
+ <ControlInput v-model="workingObj.note" name="Note"
1303
+ type="textarea" :rows="3" :disabled="is_locked" />
1304
+ </div>
1305
+ </div>
1306
+ </ControlBox>
1307
+
1308
+ <!-- Action Buttons -->
1309
+ <div class="row mt-3">
1310
+ <div class="col-12">
1311
+ <ControlButton v-if="workingObj.can_edit" name="Save" type="primary" @onClick="save" />
1312
+ <ControlButton v-if="workingObj.can_submit" name="Submit" type="success" @onClick="submit" />
1313
+ <ControlButton v-if="workingObj.can_approve" name="Approve" type="success"
1314
+ @onClick="show_confirm_approve = true" />
1315
+ <ControlButton v-if="workingObj.can_reject" name="Reject" type="danger"
1316
+ @onClick="show_reject = true" />
1317
+ <ControlButton name="Back" type="secondary" @onClick="$router.go(-1)" />
1318
+ </div>
1319
+ </div>
1320
+
1321
+ <!-- Popups -->
1322
+ <ControlPopupConfirm :visible="show_confirm_delete" title="Delete?"
1323
+ message="Are you sure?" @onConfirm="delete_record" @onClose="show_confirm_delete = false" />
1324
+ <ControlPopupConfirmReject :visible="show_confirm_approve" title="Approve"
1325
+ @onConfirm="on_approve" @onClose="show_confirm_approve = false" />
1326
+ <ControlPopupReject :visible="show_reject" title="Reject"
1327
+ @onConfirm="on_reject" @onClose="show_reject = false" />
1328
+ </div>
1329
+ </template>
1330
+
1331
+ <script>
1332
+ export default {
1333
+ data() {
1334
+ return {
1335
+ workingObj: { id_agreement: "", agreement_number: "", id_branch: "",
1336
+ start_date: "", end_date: "", amount: 0, note: "",
1337
+ can_edit: true, can_submit: false, can_approve: false,
1338
+ can_reject: false, is_deleted: false },
1339
+ branchList: [],
1340
+ show_confirm_delete: false,
1341
+ show_confirm_approve: false,
1342
+ show_reject: false,
1343
+ };
1344
+ },
1345
+ computed: {
1346
+ is_locked() { return !this.workingObj.can_edit; },
1347
+ },
1348
+ async created() {
1349
+ if (!authorize_menu(this.$route.fullPath)) { this.$router.push(state.homepage); return; }
1350
+ state.page_header = t("Agreement Form");
1351
+ const id = this.$route.query.id;
1352
+ await Promise.all([
1353
+ id ? this.load_by_id(id) : Promise.resolve(),
1354
+ this.load_dropdowns(),
1355
+ ]);
1356
+ },
1357
+ methods: {
1358
+ async load_by_id(id) {
1359
+ this.workingObj = await get_agreement_by_id(id);
1360
+ },
1361
+ async load_dropdowns() {
1362
+ this.branchList = await get_branch_list_dropdown();
1363
+ },
1364
+ async save() {
1365
+ if (!this.validate()) return;
1366
+ await request("/save", { table: "agreement", key_array: ["id_agreement"], ...this.workingObj });
1367
+ show_message("success", t("Saved successfully"));
1368
+ },
1369
+ async delete_record() {
1370
+ this.show_confirm_delete = false;
1371
+ await request("/save", { table: "agreement", key_array: ["id_agreement"],
1372
+ id_agreement: this.workingObj.id_agreement, is_deleted: true });
1373
+ this.$router.push("/Agreement/AgreementList");
1374
+ },
1375
+ async on_approve(reason, is_adhoc, is_onplan) {
1376
+ this.show_confirm_approve = false;
1377
+ await request_origin("/api/agreement/approve", {
1378
+ schema: get_schema(),
1379
+ id_agreement: this.workingObj.id_agreement,
1380
+ username: state.username, // ✅ luôn truyền username cho custom approve endpoints
1381
+ reason, is_adhoc, is_onplan,
1382
+ });
1383
+ await this.load_by_id(this.workingObj.id_agreement);
1384
+ },
1385
+ async on_reject(reason, is_adhoc, is_onplan) {
1386
+ this.show_reject = false;
1387
+ await request("/save", { table: "agreement", key_array: ["id_agreement"],
1388
+ id_agreement: this.workingObj.id_agreement, status: "rejected", reject_reason: reason });
1389
+ await this.load_by_id(this.workingObj.id_agreement);
1390
+ },
1391
+ validate() {
1392
+ const required = [
1393
+ { key: "agreement_number", name: t("Agreement Number") },
1394
+ { key: "id_branch", name: t("Branch") },
1395
+ { key: "start_date", name: t("Start Date") },
1396
+ ];
1397
+ for (const f of required) {
1398
+ if (check_is_null_or_blank(this.workingObj[f.key])) {
1399
+ show_message("error", `${f.name} ${t("is required")}`); return false;
1400
+ }
1401
+ }
1402
+ return true;
1403
+ },
1404
+ },
1405
+ };
1406
+ </script>
1407
+ ```
1408
+
1409
+ ---
1410
+
1411
+ ## 12. Routing & Navigation
1412
+
1413
+ ```javascript
1414
+ // Navigate with query
1415
+ this.$router.push({ path: "/Agreement/AgreementForm", query: { id: row.id_agreement } });
1416
+
1417
+ // Get params
1418
+ const id = this.$route.query.id;
1419
+
1420
+ // Back
1421
+ this.$router.go(-1);
1422
+ this.$router.push("/Agreement/AgreementList");
1423
+
1424
+ // Utility (từ utils.js — auto-imported)
1425
+ go_to_page("/Agreement/AgreementForm", { id: "uuid" });
1426
+ go_back("/Agreement/AgreementList");
1427
+ ```
1428
+
1429
+ ---
1430
+
1431
+ ## 13. Validation Pattern
1432
+
1433
+ ```javascript
1434
+ validate() {
1435
+ // Required fields
1436
+ const required = [
1437
+ { key: "agreement_number", name: t("Agreement Number") },
1438
+ { key: "id_branch", name: t("Branch") },
1439
+ { key: "start_date", name: t("Start Date") },
1440
+ ];
1441
+ for (const f of required) {
1442
+ if (check_is_null_or_blank(this.workingObj[f.key])) {
1443
+ show_message("error", `${f.name} ${t("is required")}`);
1444
+ return false;
1445
+ }
1446
+ }
1447
+
1448
+ // Business rules
1449
+ if (this.workingObj.end_date && this.workingObj.start_date) {
1450
+ if (this.workingObj.end_date < this.workingObj.start_date) {
1451
+ show_message("error", t("End date must be after start date"));
1452
+ return false;
1453
+ }
1454
+ }
1455
+
1456
+ if (check_is_zero(this.workingObj.amount)) {
1457
+ show_message("error", t("Amount must be greater than 0"));
1458
+ return false;
1459
+ }
1460
+
1461
+ return true;
1462
+ },
1463
+ ```
1464
+
1465
+ ---
1466
+
1467
+ ## 14. Đề Xuất Components Mới
1468
+
1469
+ > **Lưu ý:** Chỉ liệt kê những component thực sự chưa có.
1470
+ > - Number input → dùng `ControlInput type="number"`
1471
+ > - Search input → dùng `ControlInput type="search"`
1472
+ > - Radio group → dùng `ControlCheckbox` với layout ngang là đủ
1473
+
1474
+ ---
1475
+
1476
+ ### 🔮 Tạo khi thực sự cần dùng
1477
+
1478
+ #### `ControlStatusBadge` — Hiển thị status với màu sắc theo map
1479
+ ```vue
1480
+ <ControlStatusBadge :status="row.status"
1481
+ :map="{
1482
+ draft: { label: 'Draft', color: 'gray' },
1483
+ approved: { label: 'Approved', color: 'green' },
1484
+ rejected: { label: 'Rejected', color: 'red' },
1485
+ }"
1486
+ />
1487
+ ```
1488
+
1489
+ #### `ControlFileList` — Hiển thị + download danh sách files đã upload
1490
+ ```vue
1491
+ <ControlFileList
1492
+ :files="workingObj.file_list"
1493
+ :can_delete="workingObj.can_edit"
1494
+ @onDelete="remove_file"
1495
+ />
1496
+ ```
1497
+
1498
+ #### `ControlSummaryCard` — KPI/summary card cho dashboard
1499
+ ```vue
1500
+ <ControlSummaryCard title="Total Amount" :value="total_amount" format="number" color="primary" />
1501
+ ```
1502
+
1503
+ #### `GridCardList` — Hiển thị dạng card thay vì table (mobile / dashboard)
1504
+ ```vue
1505
+ <GridCardList :rows="rows" :fields="['name', 'amount', 'status']" @onCardClick="go_to_detail" />
1506
+ ```