@rashidee/co2 1.2.5

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 (190) hide show
  1. package/dist/index.js +25449 -0
  2. package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
  3. package/drizzle/0001_wise_wolf_cub.sql +13 -0
  4. package/drizzle/0002_secret_spyke.sql +16 -0
  5. package/drizzle/0003_workspace_config_singleton.sql +16 -0
  6. package/drizzle/0004_terminal_claim_v120.sql +11 -0
  7. package/drizzle/0005_brainstorm_v124.sql +10 -0
  8. package/drizzle/0006_project_v140.sql +6 -0
  9. package/drizzle/0007_prd_wizard_v124.sql +14 -0
  10. package/drizzle/0008_workflow_artifact_v126.sql +24 -0
  11. package/drizzle/0009_project_import_v150.sql +7 -0
  12. package/drizzle/0010_quality_v151.sql +3 -0
  13. package/drizzle/meta/0000_snapshot.json +1335 -0
  14. package/drizzle/meta/0001_snapshot.json +1416 -0
  15. package/drizzle/meta/0002_snapshot.json +1510 -0
  16. package/drizzle/meta/_journal.json +83 -0
  17. package/package.json +41 -0
  18. package/plugin/.claude-plugin/marketplace.json +36 -0
  19. package/plugin/.claude-plugin/plugin.json +22 -0
  20. package/plugin/README.md +625 -0
  21. package/plugin/SKILLS.md +350 -0
  22. package/plugin/co2.svg +5 -0
  23. package/plugin/commands/brainstorm-loop.md +129 -0
  24. package/plugin/skills/conductor-defect/SKILL.md +898 -0
  25. package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
  26. package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
  27. package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
  28. package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
  29. package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
  30. package/plugin/skills/depgen-k8s/SKILL.md +468 -0
  31. package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
  32. package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
  33. package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
  34. package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
  35. package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
  36. package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
  37. package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
  38. package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
  39. package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
  40. package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
  41. package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
  42. package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
  43. package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
  44. package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
  45. package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
  46. package/plugin/skills/modelgen-relational/SKILL.md +707 -0
  47. package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
  48. package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
  49. package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
  50. package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
  51. package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
  52. package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
  53. package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
  54. package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
  55. package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
  56. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
  57. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
  58. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
  59. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
  60. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
  61. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
  62. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
  63. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
  64. package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
  65. package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
  66. package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
  67. package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
  68. package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
  69. package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
  70. package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
  71. package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
  72. package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
  73. package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
  74. package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
  75. package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
  76. package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
  77. package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
  78. package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
  79. package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
  80. package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
  81. package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
  82. package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
  83. package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
  84. package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
  85. package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
  86. package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
  87. package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
  88. package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
  89. package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
  90. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
  91. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
  92. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
  93. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
  94. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
  95. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
  96. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
  97. package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
  98. package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
  99. package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
  100. package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
  101. package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
  102. package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
  103. package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
  104. package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
  105. package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
  106. package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
  107. package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
  108. package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
  109. package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
  110. package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
  111. package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
  112. package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
  113. package/plugin/skills/testgen-functional/SKILL.md +1381 -0
  114. package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
  115. package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
  116. package/plugin/skills/util-projectinit/SKILL.md +307 -0
  117. package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
  118. package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
  119. package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
  120. package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
  121. package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
  122. package/plugin/skills/util-projectsync/SKILL.md +501 -0
  123. package/plugin/skills/util-updprd/SKILL.md +324 -0
  124. package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
  125. package/plugin/skills/util-ustagger/SKILL.md +154 -0
  126. package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
  127. package/static/assets/arc-DRKw7sjs.js +1 -0
  128. package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
  129. package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
  130. package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
  131. package/static/assets/channel-C2nVbuwv.js +1 -0
  132. package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
  133. package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
  134. package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
  135. package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
  136. package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
  137. package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
  138. package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
  139. package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
  140. package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
  141. package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
  142. package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
  143. package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
  144. package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
  145. package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
  146. package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
  147. package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
  148. package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
  149. package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  150. package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
  151. package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
  152. package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
  153. package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
  154. package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
  155. package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
  156. package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
  157. package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
  158. package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
  159. package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
  160. package/static/assets/graph-C9eacEi8.js +1 -0
  161. package/static/assets/index-LZSQkLE2.css +41 -0
  162. package/static/assets/index-y7XDSgMG.js +495 -0
  163. package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
  164. package/static/assets/init-Gi6I4Gst.js +1 -0
  165. package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
  166. package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
  167. package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
  168. package/static/assets/katex-C5jXJg4s.js +257 -0
  169. package/static/assets/layout-DEXfKzaS.js +1 -0
  170. package/static/assets/linear-CLO--ryk.js +1 -0
  171. package/static/assets/map-Czzmt4hB.js +1 -0
  172. package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
  173. package/static/assets/ordinal-Cboi1Yqb.js +1 -0
  174. package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
  175. package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
  176. package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
  177. package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
  178. package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
  179. package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
  180. package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
  181. package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
  182. package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
  183. package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
  184. package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
  185. package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
  186. package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
  187. package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
  188. package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
  189. package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
  190. package/static/index.html +24 -0
@@ -0,0 +1,1467 @@
1
+ # Admin Layout Architecture — Vite + React 19 + shadcn/ui
2
+
3
+ The mockup application uses **Vite** as the build tool (dev server for design iteration
4
+ only), **React 19** with **TypeScript** for UI components, **React Router v7** for
5
+ client-side navigation, **shadcn/ui** (Radix UI + Tailwind CSS) for the component library,
6
+ **Lucide React** for icons, and **next-themes** for dark mode support.
7
+
8
+ The app is generated with `base`/`basename` = `/{{APP_SLUG}}/` and **built**
9
+ (`npm run build`); the shared **Mockup Hub** (`<root>/mockup/server.js` — see
10
+ [mockup-hub-template.md](mockup-hub-template.md)) serves the `dist/` output at
11
+ `/{{APP_SLUG}}/...` with SPA fallback. `{{APP_SLUG}}` is replaced at generation time with
12
+ the app folder name minus its leading `<number>_` prefix.
13
+
14
+ ---
15
+
16
+ ## File Structure
17
+
18
+ ```
19
+ mockup/
20
+ .gitignore
21
+ mockup-manifest.json # Hub discovery manifest (see mockup-hub-template.md)
22
+ dist/ # Built output served by the hub (npm run build; gitignored)
23
+ package.json
24
+ vite.config.ts
25
+ tsconfig.json
26
+ tsconfig.app.json
27
+ tsconfig.node.json
28
+ tailwind.config.js
29
+ postcss.config.js
30
+ components.json
31
+ index.html
32
+ MOCKUP.html
33
+ public/
34
+ reports/ # Report layouts (if applicable)
35
+ src/
36
+ main.tsx
37
+ App.tsx
38
+ index.css
39
+ lib/
40
+ utils.ts
41
+ components/
42
+ ui/
43
+ button.tsx
44
+ card.tsx
45
+ table.tsx
46
+ badge.tsx
47
+ input.tsx
48
+ label.tsx
49
+ select.tsx
50
+ dialog.tsx
51
+ dropdown-menu.tsx
52
+ avatar.tsx
53
+ separator.tsx
54
+ tabs.tsx
55
+ switch.tsx
56
+ tooltip.tsx
57
+ breadcrumb.tsx
58
+ pagination.tsx
59
+ sheet.tsx
60
+ layout/
61
+ app-layout.tsx
62
+ app-header.tsx
63
+ app-sidebar.tsx
64
+ app-footer.tsx
65
+ sidebar-config.ts
66
+ pages/
67
+ {role-kebab-case}/
68
+ home.tsx
69
+ profile.tsx
70
+ account.tsx
71
+ notifications.tsx
72
+ {module-kebab-case}.tsx
73
+ {module-kebab-case}-detail.tsx
74
+ {module-kebab-case}-create.tsx
75
+ {module-kebab-case}-edit.tsx
76
+ ...
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Placeholders Reference
82
+
83
+ | Placeholder | Description |
84
+ |-------------|-------------|
85
+ | `{{APP_NAME}}` | Application name (e.g., "Hub Middleware") |
86
+ | `{{APP_SLUG}}` | App slug used as URL base path (e.g., "hub_middleware") — replaced at generation time |
87
+ | `{{APP_INITIALS}}` | Application initials (e.g., "HM") |
88
+ | `{{PAGE_TITLE}}` | Screen title (e.g., "Location Information") |
89
+ | `{{COPYRIGHT_YEAR}}` | Current year |
90
+ | `{{VERSION}}` | Latest version from PRD.md |
91
+ | `{{DESIGN_PRIMARY}}` | Primary color hex from design system |
92
+ | `{{DESIGN_SECONDARY}}` | Secondary color hex from design system |
93
+ | `{{DESIGN_ACCENT}}` | Accent/CTA color hex |
94
+ | `{{HEADING_FONT}}` | Heading font family |
95
+ | `{{BODY_FONT}}` | Body font family |
96
+ | `{{GOOGLE_FONTS_URL}}` | Google Fonts import URL |
97
+
98
+ ---
99
+
100
+ ## package.json Template
101
+
102
+ ```json
103
+ {
104
+ "name": "{{APP_SLUG}}-mockup",
105
+ "private": true,
106
+ "version": "1.0.0",
107
+ "type": "module",
108
+ "description": "UI Mockup (React + shadcn/ui) — generated by mockgen-shadcn; built dist/ is served by the Mockup Hub at <root>/mockup",
109
+ "scripts": {
110
+ "dev": "vite",
111
+ "build": "tsc -b && vite build",
112
+ "preview": "vite preview"
113
+ },
114
+ "dependencies": {
115
+ "react": "^19.0.0",
116
+ "react-dom": "^19.0.0",
117
+ "react-router-dom": "^7.1.0",
118
+ "lucide-react": "^0.468.0",
119
+ "next-themes": "^0.4.4",
120
+ "@radix-ui/react-avatar": "^1.1.2",
121
+ "@radix-ui/react-dialog": "^1.1.4",
122
+ "@radix-ui/react-dropdown-menu": "^2.1.4",
123
+ "@radix-ui/react-label": "^2.1.1",
124
+ "@radix-ui/react-select": "^2.1.4",
125
+ "@radix-ui/react-separator": "^1.1.1",
126
+ "@radix-ui/react-switch": "^1.1.2",
127
+ "@radix-ui/react-tabs": "^1.1.2",
128
+ "@radix-ui/react-tooltip": "^1.1.6",
129
+ "@radix-ui/react-slot": "^1.1.1",
130
+ "class-variance-authority": "^0.7.1",
131
+ "clsx": "^2.1.1",
132
+ "tailwind-merge": "^2.6.0"
133
+ },
134
+ "devDependencies": {
135
+ "@types/react": "^19.0.0",
136
+ "@types/react-dom": "^19.0.0",
137
+ "@vitejs/plugin-react": "^4.3.4",
138
+ "autoprefixer": "^10.4.20",
139
+ "postcss": "^8.4.49",
140
+ "tailwindcss": "^3.4.17",
141
+ "typescript": "~5.7.0",
142
+ "vite": "^6.0.0"
143
+ }
144
+ }
145
+ ```
146
+
147
+ ---
148
+
149
+ ## vite.config.ts Template
150
+
151
+ The `base` option is REQUIRED — it makes the built app servable by the Mockup Hub at
152
+ `/{{APP_SLUG}}/` (and the dev server serve at `http://localhost:5173/{{APP_SLUG}}/`).
153
+
154
+ ```typescript
155
+ import { defineConfig } from "vite";
156
+ import react from "@vitejs/plugin-react";
157
+ import path from "path";
158
+
159
+ export default defineConfig({
160
+ base: "/{{APP_SLUG}}/",
161
+ plugins: [react()],
162
+ resolve: {
163
+ alias: {
164
+ "@": path.resolve(__dirname, "./src"),
165
+ },
166
+ },
167
+ });
168
+ ```
169
+
170
+ ---
171
+
172
+ ## tsconfig.json Template
173
+
174
+ ```json
175
+ {
176
+ "files": [],
177
+ "references": [
178
+ { "path": "./tsconfig.app.json" },
179
+ { "path": "./tsconfig.node.json" }
180
+ ]
181
+ }
182
+ ```
183
+
184
+ ## tsconfig.app.json Template
185
+
186
+ ```json
187
+ {
188
+ "compilerOptions": {
189
+ "target": "ES2020",
190
+ "useDefineForClassFields": true,
191
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
192
+ "module": "ESNext",
193
+ "skipLibCheck": true,
194
+ "moduleResolution": "bundler",
195
+ "allowImportingTsExtensions": true,
196
+ "isolatedModules": true,
197
+ "moduleDetection": "force",
198
+ "noEmit": true,
199
+ "jsx": "react-jsx",
200
+ "strict": true,
201
+ "noUnusedLocals": false,
202
+ "noUnusedParameters": false,
203
+ "noFallthroughCasesInSwitch": true,
204
+ "noUncheckedSideEffectImports": true,
205
+ "baseUrl": ".",
206
+ "paths": {
207
+ "@/*": ["./src/*"]
208
+ }
209
+ },
210
+ "include": ["src"]
211
+ }
212
+ ```
213
+
214
+ ## tsconfig.node.json Template
215
+
216
+ ```json
217
+ {
218
+ "compilerOptions": {
219
+ "target": "ES2022",
220
+ "lib": ["ES2023"],
221
+ "module": "ESNext",
222
+ "skipLibCheck": true,
223
+ "moduleResolution": "bundler",
224
+ "allowImportingTsExtensions": true,
225
+ "isolatedModules": true,
226
+ "moduleDetection": "force",
227
+ "noEmit": true,
228
+ "strict": true,
229
+ "noUnusedLocals": false,
230
+ "noUnusedParameters": false,
231
+ "noFallthroughCasesInSwitch": true,
232
+ "noUncheckedSideEffectImports": true
233
+ },
234
+ "include": ["vite.config.ts"]
235
+ }
236
+ ```
237
+
238
+ ---
239
+
240
+ ## tailwind.config.js Template
241
+
242
+ ```javascript
243
+ /** @type {import('tailwindcss').Config} */
244
+ export default {
245
+ darkMode: ["class"],
246
+ content: [
247
+ "./index.html",
248
+ "./src/**/*.{ts,tsx}",
249
+ ],
250
+ theme: {
251
+ extend: {
252
+ colors: {
253
+ border: "hsl(var(--border))",
254
+ input: "hsl(var(--input))",
255
+ ring: "hsl(var(--ring))",
256
+ background: "hsl(var(--background))",
257
+ foreground: "hsl(var(--foreground))",
258
+ primary: {
259
+ DEFAULT: "hsl(var(--primary))",
260
+ foreground: "hsl(var(--primary-foreground))",
261
+ },
262
+ secondary: {
263
+ DEFAULT: "hsl(var(--secondary))",
264
+ foreground: "hsl(var(--secondary-foreground))",
265
+ },
266
+ destructive: {
267
+ DEFAULT: "hsl(var(--destructive))",
268
+ foreground: "hsl(var(--destructive-foreground))",
269
+ },
270
+ muted: {
271
+ DEFAULT: "hsl(var(--muted))",
272
+ foreground: "hsl(var(--muted-foreground))",
273
+ },
274
+ accent: {
275
+ DEFAULT: "hsl(var(--accent))",
276
+ foreground: "hsl(var(--accent-foreground))",
277
+ },
278
+ popover: {
279
+ DEFAULT: "hsl(var(--popover))",
280
+ foreground: "hsl(var(--popover-foreground))",
281
+ },
282
+ card: {
283
+ DEFAULT: "hsl(var(--card))",
284
+ foreground: "hsl(var(--card-foreground))",
285
+ },
286
+ },
287
+ borderRadius: {
288
+ lg: "var(--radius)",
289
+ md: "calc(var(--radius) - 2px)",
290
+ sm: "calc(var(--radius) - 4px)",
291
+ },
292
+ fontFamily: {
293
+ heading: ["{{HEADING_FONT}}", "sans-serif"],
294
+ body: ["{{BODY_FONT}}", "sans-serif"],
295
+ },
296
+ },
297
+ },
298
+ plugins: [],
299
+ };
300
+ ```
301
+
302
+ ---
303
+
304
+ ## postcss.config.js Template
305
+
306
+ ```javascript
307
+ export default {
308
+ plugins: {
309
+ tailwindcss: {},
310
+ autoprefixer: {},
311
+ },
312
+ };
313
+ ```
314
+
315
+ ---
316
+
317
+ ## components.json Template (shadcn/ui config)
318
+
319
+ ```json
320
+ {
321
+ "$schema": "https://ui.shadcn.com/schema.json",
322
+ "style": "new-york",
323
+ "rsc": false,
324
+ "tsx": true,
325
+ "tailwind": {
326
+ "config": "tailwind.config.js",
327
+ "css": "src/index.css",
328
+ "baseColor": "zinc",
329
+ "cssVariables": true
330
+ },
331
+ "aliases": {
332
+ "components": "@/components",
333
+ "utils": "@/lib/utils",
334
+ "ui": "@/components/ui"
335
+ }
336
+ }
337
+ ```
338
+
339
+ ---
340
+
341
+ ## index.html Template (Vite entry)
342
+
343
+ ```html
344
+ <!DOCTYPE html>
345
+ <html lang="en">
346
+ <head>
347
+ <meta charset="UTF-8" />
348
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
349
+ <title>{{APP_NAME}}</title>
350
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
351
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
352
+ <link href="{{GOOGLE_FONTS_URL}}" rel="stylesheet" />
353
+ </head>
354
+ <body>
355
+ <div id="root"></div>
356
+ <script type="module" src="/src/main.tsx"></script>
357
+ </body>
358
+ </html>
359
+ ```
360
+
361
+ ---
362
+
363
+ ## src/index.css Template
364
+
365
+ The CSS variables define the shadcn/ui color system. Replace HSL values to match the
366
+ design system from Step 2. The variable names are used by all shadcn/ui components.
367
+
368
+ ```css
369
+ @tailwind base;
370
+ @tailwind components;
371
+ @tailwind utilities;
372
+
373
+ @layer base {
374
+ :root {
375
+ --background: 0 0% 100%;
376
+ --foreground: 240 10% 3.9%;
377
+ --card: 0 0% 100%;
378
+ --card-foreground: 240 10% 3.9%;
379
+ --popover: 0 0% 100%;
380
+ --popover-foreground: 240 10% 3.9%;
381
+ --primary: 240 5.9% 10%;
382
+ --primary-foreground: 0 0% 98%;
383
+ --secondary: 240 4.8% 95.9%;
384
+ --secondary-foreground: 240 5.9% 10%;
385
+ --muted: 240 4.8% 95.9%;
386
+ --muted-foreground: 240 3.8% 46.1%;
387
+ --accent: 240 4.8% 95.9%;
388
+ --accent-foreground: 240 5.9% 10%;
389
+ --destructive: 0 84.2% 60.2%;
390
+ --destructive-foreground: 0 0% 98%;
391
+ --border: 240 5.9% 90%;
392
+ --input: 240 5.9% 90%;
393
+ --ring: 240 5.9% 10%;
394
+ --radius: 0.5rem;
395
+ }
396
+
397
+ .dark {
398
+ --background: 240 10% 3.9%;
399
+ --foreground: 0 0% 98%;
400
+ --card: 240 10% 3.9%;
401
+ --card-foreground: 0 0% 98%;
402
+ --popover: 240 10% 3.9%;
403
+ --popover-foreground: 0 0% 98%;
404
+ --primary: 0 0% 98%;
405
+ --primary-foreground: 240 5.9% 10%;
406
+ --secondary: 240 3.7% 15.9%;
407
+ --secondary-foreground: 0 0% 98%;
408
+ --muted: 240 3.7% 15.9%;
409
+ --muted-foreground: 240 5% 64.9%;
410
+ --accent: 240 3.7% 15.9%;
411
+ --accent-foreground: 0 0% 98%;
412
+ --destructive: 0 62.8% 30.6%;
413
+ --destructive-foreground: 0 0% 98%;
414
+ --border: 240 3.7% 15.9%;
415
+ --input: 240 3.7% 15.9%;
416
+ --ring: 240 4.9% 83.9%;
417
+ }
418
+ }
419
+
420
+ @layer base {
421
+ * {
422
+ @apply border-border;
423
+ }
424
+ body {
425
+ @apply bg-background text-foreground;
426
+ font-family: "{{BODY_FONT}}", sans-serif;
427
+ }
428
+ h1, h2, h3, h4, h5, h6 {
429
+ font-family: "{{HEADING_FONT}}", sans-serif;
430
+ }
431
+ }
432
+ ```
433
+
434
+ > **Design system integration**: When a design system provides custom colors (hex),
435
+ > convert them to HSL values for the CSS variables. For example, primary color `#1E40AF`
436
+ > becomes `--primary: 221.2 83.2% 53.3%`. Apply the design system's primary, secondary,
437
+ > and accent colors to the corresponding CSS variables.
438
+
439
+ ---
440
+
441
+ ## src/lib/utils.ts Template
442
+
443
+ ```typescript
444
+ import { type ClassValue, clsx } from "clsx";
445
+ import { twMerge } from "tailwind-merge";
446
+
447
+ export function cn(...inputs: ClassValue[]) {
448
+ return twMerge(clsx(inputs));
449
+ }
450
+ ```
451
+
452
+ ---
453
+
454
+ ## src/main.tsx Template
455
+
456
+ ```tsx
457
+ import { StrictMode } from "react";
458
+ import { createRoot } from "react-dom/client";
459
+ import { BrowserRouter } from "react-router-dom";
460
+ import { ThemeProvider } from "next-themes";
461
+ import App from "./App";
462
+ import "./index.css";
463
+
464
+ createRoot(document.getElementById("root")!).render(
465
+ <StrictMode>
466
+ <BrowserRouter basename="/{{APP_SLUG}}">
467
+ <ThemeProvider attribute="class" defaultTheme="light" enableSystem>
468
+ <App />
469
+ </ThemeProvider>
470
+ </BrowserRouter>
471
+ </StrictMode>
472
+ );
473
+ ```
474
+
475
+ > The `basename` MUST match the Vite `base` — all internal `<Link to="/{role}/...">`
476
+ > paths stay unchanged; React Router resolves them under `/{{APP_SLUG}}` automatically.
477
+
478
+ ---
479
+
480
+ ## src/App.tsx Template
481
+
482
+ Defines all routes using React Router. The `AppLayout` component wraps all authenticated
483
+ role pages.
484
+
485
+ ```tsx
486
+ import { Routes, Route, Navigate } from "react-router-dom";
487
+ import { AppLayout } from "@/components/layout/app-layout";
488
+
489
+ // Import all page components — grouped by role
490
+ // === Hub Administrator ===
491
+ import HubAdminHome from "@/pages/hub-administrator/home";
492
+ import HubAdminProfile from "@/pages/hub-administrator/profile";
493
+ import HubAdminAccount from "@/pages/hub-administrator/account";
494
+ import HubAdminNotifications from "@/pages/hub-administrator/notifications";
495
+ // ... module pages ...
496
+
497
+ // === Hub Operation Support ===
498
+ // ... repeat for each role ...
499
+
500
+ function App() {
501
+ return (
502
+ <Routes>
503
+ {/* Landing — redirect to first role's home */}
504
+ <Route path="/" element={<Navigate to="/hub-administrator/home" replace />} />
505
+
506
+ {/* Hub Administrator */}
507
+ <Route path="/hub-administrator" element={<AppLayout role="hub-administrator" />}>
508
+ <Route index element={<Navigate to="home" replace />} />
509
+ <Route path="home" element={<HubAdminHome />} />
510
+ <Route path="profile" element={<HubAdminProfile />} />
511
+ <Route path="account" element={<HubAdminAccount />} />
512
+ <Route path="notifications" element={<HubAdminNotifications />} />
513
+ {/* Module pages */}
514
+ {/* <Route path="location-information" element={<HubAdminLocationInformation />} /> */}
515
+ {/* <Route path="location-information-detail" element={<HubAdminLocationInformationDetail />} /> */}
516
+ {/* ... */}
517
+ </Route>
518
+
519
+ {/* Hub Operation Support */}
520
+ {/* <Route path="/hub-operation-support" element={<AppLayout role="hub-operation-support" />}> */}
521
+ {/* ... */}
522
+ {/* </Route> */}
523
+
524
+ {/* Catch-all */}
525
+ <Route path="*" element={<Navigate to="/" replace />} />
526
+ </Routes>
527
+ );
528
+ }
529
+
530
+ export default App;
531
+ ```
532
+
533
+ > **Route generation**: For each role and page in the screen plan, generate a complete
534
+ > import statement and `<Route>` entry. The comments above are placeholders — replace
535
+ > with actual routes.
536
+
537
+ ---
538
+
539
+ ## src/components/layout/app-layout.tsx Template
540
+
541
+ ```tsx
542
+ import { useState } from "react";
543
+ import { Outlet } from "react-router-dom";
544
+ import { AppHeader } from "./app-header";
545
+ import { AppSidebar } from "./app-sidebar";
546
+ import { AppFooter } from "./app-footer";
547
+
548
+ interface AppLayoutProps {
549
+ role: string;
550
+ }
551
+
552
+ export function AppLayout({ role }: AppLayoutProps) {
553
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
554
+
555
+ return (
556
+ <div className="min-h-screen flex flex-col bg-background">
557
+ {/* Header */}
558
+ <AppHeader role={role} />
559
+
560
+ {/* Body: Sidebar + Main */}
561
+ <div className="flex pt-16 min-h-[calc(100vh-4rem)]">
562
+ {/* Sidebar */}
563
+ <AppSidebar
564
+ role={role}
565
+ collapsed={sidebarCollapsed}
566
+ onToggle={() => setSidebarCollapsed(!sidebarCollapsed)}
567
+ />
568
+
569
+ {/* Main content */}
570
+ <main
571
+ className={cn(
572
+ "flex-1 flex flex-col min-w-0 transition-all duration-200",
573
+ sidebarCollapsed ? "ml-16" : "ml-64"
574
+ )}
575
+ >
576
+ {/* Page content (React Router Outlet) */}
577
+ <div className="flex-1 flex flex-col">
578
+ <Outlet />
579
+ </div>
580
+
581
+ {/* Footer */}
582
+ <AppFooter />
583
+ </main>
584
+ </div>
585
+ </div>
586
+ );
587
+ }
588
+ ```
589
+
590
+ > **Note**: Import `cn` from `@/lib/utils` at the top of the file.
591
+
592
+ ---
593
+
594
+ ## src/components/layout/app-header.tsx Template
595
+
596
+ ```tsx
597
+ import { Link } from "react-router-dom";
598
+ import { useTheme } from "next-themes";
599
+ import {
600
+ Bell,
601
+ Globe,
602
+ Moon,
603
+ Sun,
604
+ User,
605
+ Settings,
606
+ LogOut,
607
+ } from "lucide-react";
608
+ import { Button } from "@/components/ui/button";
609
+ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
610
+ import { Badge } from "@/components/ui/badge";
611
+ import { Separator } from "@/components/ui/separator";
612
+ import {
613
+ DropdownMenu,
614
+ DropdownMenuContent,
615
+ DropdownMenuItem,
616
+ DropdownMenuLabel,
617
+ DropdownMenuSeparator,
618
+ DropdownMenuTrigger,
619
+ } from "@/components/ui/dropdown-menu";
620
+
621
+ interface AppHeaderProps {
622
+ role: string;
623
+ }
624
+
625
+ export function AppHeader({ role }: AppHeaderProps) {
626
+ const { theme, setTheme } = useTheme();
627
+
628
+ return (
629
+ <header className="bg-background border-b h-16 flex items-center justify-between px-6 fixed top-0 left-0 right-0 z-30">
630
+ {/* Left: Logo + App Name */}
631
+ <div className="flex items-center gap-3">
632
+ <div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center">
633
+ <span className="text-primary-foreground font-bold text-sm">{{APP_INITIALS}}</span>
634
+ </div>
635
+ <span className="font-semibold text-lg">{{APP_NAME}}</span>
636
+ </div>
637
+
638
+ {/* Right: Controls */}
639
+ <div className="flex items-center gap-1">
640
+ {/* Notification Bell */}
641
+ <DropdownMenu>
642
+ <DropdownMenuTrigger asChild>
643
+ <Button variant="ghost" size="icon" className="relative">
644
+ <Bell className="h-5 w-5" />
645
+ <Badge
646
+ variant="destructive"
647
+ className="absolute -top-1 -right-1 h-4 w-4 p-0 flex items-center justify-center text-[10px]"
648
+ >
649
+ 3
650
+ </Badge>
651
+ </Button>
652
+ </DropdownMenuTrigger>
653
+ <DropdownMenuContent align="end" className="w-80">
654
+ <DropdownMenuLabel className="flex items-center justify-between">
655
+ <span>Notifications</span>
656
+ <Button variant="link" size="sm" className="h-auto p-0 text-xs">
657
+ Mark all read
658
+ </Button>
659
+ </DropdownMenuLabel>
660
+ <DropdownMenuSeparator />
661
+ <DropdownMenuItem className="flex flex-col items-start gap-1 py-3">
662
+ <div className="flex items-center gap-2">
663
+ <div className="w-2 h-2 rounded-full bg-primary shrink-0" />
664
+ <span className="font-medium text-sm">New document uploaded</span>
665
+ </div>
666
+ <span className="text-xs text-muted-foreground ml-4">
667
+ A new employment contract has been uploaded for review.
668
+ </span>
669
+ <span className="text-xs text-muted-foreground ml-4">2 minutes ago</span>
670
+ </DropdownMenuItem>
671
+ <DropdownMenuItem className="flex flex-col items-start gap-1 py-3">
672
+ <div className="flex items-center gap-2">
673
+ <div className="w-2 h-2 rounded-full bg-primary shrink-0" />
674
+ <span className="font-medium text-sm">Status changed</span>
675
+ </div>
676
+ <span className="text-xs text-muted-foreground ml-4">
677
+ Record status has been updated.
678
+ </span>
679
+ <span className="text-xs text-muted-foreground ml-4">15 minutes ago</span>
680
+ </DropdownMenuItem>
681
+ <DropdownMenuSeparator />
682
+ <DropdownMenuItem asChild>
683
+ <Link
684
+ to={`/${role}/notifications`}
685
+ className="w-full text-center text-sm font-medium text-primary"
686
+ >
687
+ View all notifications
688
+ </Link>
689
+ </DropdownMenuItem>
690
+ </DropdownMenuContent>
691
+ </DropdownMenu>
692
+
693
+ {/* Locale / Globe */}
694
+ <DropdownMenu>
695
+ <DropdownMenuTrigger asChild>
696
+ <Button variant="ghost" size="icon">
697
+ <Globe className="h-5 w-5" />
698
+ </Button>
699
+ </DropdownMenuTrigger>
700
+ <DropdownMenuContent align="end" className="w-48">
701
+ <DropdownMenuLabel>Language</DropdownMenuLabel>
702
+ <DropdownMenuSeparator />
703
+ <DropdownMenuItem className="font-medium text-primary">
704
+ English (EN)
705
+ </DropdownMenuItem>
706
+ <DropdownMenuItem>Bahasa Melayu (BM)</DropdownMenuItem>
707
+ </DropdownMenuContent>
708
+ </DropdownMenu>
709
+
710
+ {/* Dark Mode Toggle */}
711
+ <Button
712
+ variant="ghost"
713
+ size="icon"
714
+ onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
715
+ >
716
+ <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
717
+ <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
718
+ </Button>
719
+
720
+ <Separator orientation="vertical" className="h-8 mx-1" />
721
+
722
+ {/* User Avatar + Dropdown */}
723
+ <DropdownMenu>
724
+ <DropdownMenuTrigger asChild>
725
+ <Button variant="ghost" className="gap-2 px-2">
726
+ <Avatar className="h-8 w-8">
727
+ <AvatarFallback className="bg-primary text-primary-foreground text-sm">
728
+ U
729
+ </AvatarFallback>
730
+ </Avatar>
731
+ </Button>
732
+ </DropdownMenuTrigger>
733
+ <DropdownMenuContent align="end" className="w-48">
734
+ <DropdownMenuItem asChild>
735
+ <Link to={`/${role}/profile`} className="flex items-center gap-2">
736
+ <User className="h-4 w-4" />
737
+ Profile
738
+ </Link>
739
+ </DropdownMenuItem>
740
+ <DropdownMenuItem asChild>
741
+ <Link to={`/${role}/account`} className="flex items-center gap-2">
742
+ <Settings className="h-4 w-4" />
743
+ Account
744
+ </Link>
745
+ </DropdownMenuItem>
746
+ <DropdownMenuSeparator />
747
+ <DropdownMenuItem asChild>
748
+ {/* Plain <a> — escapes the SPA (basename) to the hub landing page */}
749
+ <a href="/" className="flex items-center gap-2 text-destructive">
750
+ <LogOut className="h-4 w-4" />
751
+ Logout
752
+ </a>
753
+ </DropdownMenuItem>
754
+ </DropdownMenuContent>
755
+ </DropdownMenu>
756
+ </div>
757
+ </header>
758
+ );
759
+ }
760
+ ```
761
+
762
+ ---
763
+
764
+ ## src/components/layout/app-footer.tsx Template
765
+
766
+ ```tsx
767
+ import { Separator } from "@/components/ui/separator";
768
+
769
+ export function AppFooter() {
770
+ return (
771
+ <>
772
+ <Separator />
773
+ <footer className="bg-background px-6 py-3 flex items-center justify-between text-xs text-muted-foreground">
774
+ <span>&copy; {{COPYRIGHT_YEAR}} {{APP_NAME}}. All rights reserved.</span>
775
+ <span>{{VERSION}}</span>
776
+ </footer>
777
+ </>
778
+ );
779
+ }
780
+ ```
781
+
782
+ ---
783
+
784
+ ## src/components/layout/sidebar-config.ts Template
785
+
786
+ Export a configuration object defining sidebar navigation items per role. Each item has
787
+ a title, path, and Lucide icon name.
788
+
789
+ ```typescript
790
+ export interface SidebarItem {
791
+ title: string;
792
+ path: string;
793
+ icon: string;
794
+ }
795
+
796
+ export interface SidebarGroup {
797
+ label?: string;
798
+ items: SidebarItem[];
799
+ }
800
+
801
+ export interface RoleSidebarConfig {
802
+ label: string;
803
+ groups: SidebarGroup[];
804
+ reports?: { title: string; href: string }[];
805
+ }
806
+
807
+ export const sidebarConfig: Record<string, RoleSidebarConfig> = {
808
+ "hub-administrator": {
809
+ label: "Hub Administrator",
810
+ groups: [
811
+ {
812
+ items: [
813
+ { title: "Home", path: "home", icon: "Home" },
814
+ ],
815
+ },
816
+ {
817
+ label: "Menu",
818
+ items: [
819
+ // { title: "Location Information", path: "location-information", icon: "MapPin" },
820
+ // { title: "Industrial Classification", path: "industrial-classification", icon: "Tag" },
821
+ // ... one entry per module for this role
822
+ ],
823
+ },
824
+ ],
825
+ },
826
+ // ... repeat for each role
827
+ };
828
+ ```
829
+
830
+ > **Icon mapping**: Use Lucide React icon names matching the module purpose:
831
+ >
832
+ > | Module Pattern | Lucide Icon |
833
+ > |---------------|-------------|
834
+ > | Document / File | `FileText` |
835
+ > | Location / Geography | `MapPin` |
836
+ > | Corridor / Route | `ArrowLeftRight` |
837
+ > | Recruitment / Steps | `ListOrdered` |
838
+ > | Employer / Company | `Building2` |
839
+ > | Classification / Category | `Tag` |
840
+ > | Quota / Allocation | `PieChart` |
841
+ > | Job / Demand | `Briefcase` |
842
+ > | Employee / Person | `Users` |
843
+ > | Settings / Config | `Settings` |
844
+ > | Report | `FileBarChart` |
845
+
846
+ ---
847
+
848
+ ## src/components/layout/app-sidebar.tsx Template
849
+
850
+ ```tsx
851
+ import { Link, useLocation } from "react-router-dom";
852
+ import {
853
+ Home,
854
+ MapPin,
855
+ Tag,
856
+ Building2,
857
+ Briefcase,
858
+ Users,
859
+ Settings,
860
+ FileText,
861
+ ListOrdered,
862
+ ArrowLeftRight,
863
+ PieChart,
864
+ FileBarChart,
865
+ ChevronsLeft,
866
+ ChevronsRight,
867
+ } from "lucide-react";
868
+ import { cn } from "@/lib/utils";
869
+ import { Button } from "@/components/ui/button";
870
+ import { Separator } from "@/components/ui/separator";
871
+ import {
872
+ Tooltip,
873
+ TooltipContent,
874
+ TooltipProvider,
875
+ TooltipTrigger,
876
+ } from "@/components/ui/tooltip";
877
+ import { sidebarConfig } from "./sidebar-config";
878
+
879
+ const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
880
+ Home,
881
+ MapPin,
882
+ Tag,
883
+ Building2,
884
+ Briefcase,
885
+ Users,
886
+ Settings,
887
+ FileText,
888
+ ListOrdered,
889
+ ArrowLeftRight,
890
+ PieChart,
891
+ FileBarChart,
892
+ };
893
+
894
+ interface AppSidebarProps {
895
+ role: string;
896
+ collapsed: boolean;
897
+ onToggle: () => void;
898
+ }
899
+
900
+ export function AppSidebar({ role, collapsed, onToggle }: AppSidebarProps) {
901
+ const location = useLocation();
902
+ const config = sidebarConfig[role];
903
+
904
+ if (!config) return null;
905
+
906
+ return (
907
+ <aside
908
+ className={cn(
909
+ "bg-background border-r fixed top-16 bottom-0 left-0 overflow-y-auto z-20 transition-all duration-200 flex flex-col",
910
+ collapsed ? "w-16" : "w-64"
911
+ )}
912
+ >
913
+ <nav className="flex-1 py-4">
914
+ <TooltipProvider delayDuration={0}>
915
+ {config.groups.map((group, gi) => (
916
+ <div key={gi}>
917
+ {group.label && !collapsed && (
918
+ <div className="px-4 pt-4 pb-1">
919
+ <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
920
+ {group.label}
921
+ </span>
922
+ </div>
923
+ )}
924
+ {group.label && collapsed && (
925
+ <Separator className="my-2 mx-2" />
926
+ )}
927
+ {group.items.map((item) => {
928
+ const Icon = iconMap[item.icon] || FileText;
929
+ const fullPath = `/${role}/${item.path}`;
930
+ const isActive = location.pathname.startsWith(fullPath);
931
+
932
+ const linkContent = (
933
+ <Link
934
+ to={fullPath}
935
+ className={cn(
936
+ "flex items-center gap-3 px-4 py-2.5 text-sm transition-colors duration-200",
937
+ collapsed && "justify-center px-0",
938
+ isActive
939
+ ? "bg-primary/5 text-primary border-r-2 border-primary font-medium"
940
+ : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
941
+ )}
942
+ >
943
+ <Icon className="h-5 w-5 shrink-0" />
944
+ {!collapsed && <span>{item.title}</span>}
945
+ </Link>
946
+ );
947
+
948
+ if (collapsed) {
949
+ return (
950
+ <Tooltip key={item.path}>
951
+ <TooltipTrigger asChild>{linkContent}</TooltipTrigger>
952
+ <TooltipContent side="right">{item.title}</TooltipContent>
953
+ </Tooltip>
954
+ );
955
+ }
956
+ return <div key={item.path}>{linkContent}</div>;
957
+ })}
958
+ </div>
959
+ ))}
960
+
961
+ {/* Reports section (if applicable) */}
962
+ {config.reports && config.reports.length > 0 && (
963
+ <div>
964
+ {!collapsed && (
965
+ <div className="px-4 pt-4 pb-1">
966
+ <span className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
967
+ Reports
968
+ </span>
969
+ </div>
970
+ )}
971
+ {collapsed && <Separator className="my-2 mx-2" />}
972
+ {config.reports.map((report) => {
973
+ const linkContent = (
974
+ <a
975
+ href={report.href}
976
+ target="_blank"
977
+ rel="noopener noreferrer"
978
+ className={cn(
979
+ "flex items-center gap-3 px-4 py-2.5 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors duration-200",
980
+ collapsed && "justify-center px-0"
981
+ )}
982
+ >
983
+ <FileBarChart className="h-5 w-5 shrink-0" />
984
+ {!collapsed && <span>{report.title}</span>}
985
+ </a>
986
+ );
987
+
988
+ if (collapsed) {
989
+ return (
990
+ <Tooltip key={report.title}>
991
+ <TooltipTrigger asChild>{linkContent}</TooltipTrigger>
992
+ <TooltipContent side="right">{report.title}</TooltipContent>
993
+ </Tooltip>
994
+ );
995
+ }
996
+ return <div key={report.title}>{linkContent}</div>;
997
+ })}
998
+ </div>
999
+ )}
1000
+ </TooltipProvider>
1001
+ </nav>
1002
+
1003
+ {/* Collapse toggle */}
1004
+ <div className="p-2 border-t">
1005
+ <Button
1006
+ variant="ghost"
1007
+ size="sm"
1008
+ onClick={onToggle}
1009
+ className="w-full"
1010
+ >
1011
+ {collapsed ? (
1012
+ <ChevronsRight className="h-4 w-4" />
1013
+ ) : (
1014
+ <>
1015
+ <ChevronsLeft className="h-4 w-4 mr-2" />
1016
+ <span className="text-xs">Collapse</span>
1017
+ </>
1018
+ )}
1019
+ </Button>
1020
+ </div>
1021
+ </aside>
1022
+ );
1023
+ }
1024
+ ```
1025
+
1026
+ ---
1027
+
1028
+ ## Page Component Template
1029
+
1030
+ Page components return only content — the layout wrapper (header, sidebar, footer)
1031
+ is handled by `AppLayout` via React Router's `<Outlet>`.
1032
+
1033
+ ```tsx
1034
+ // {module-name}.tsx — {Role Display Name}
1035
+ // User story traceability: {US_TAG} [{US_VERSION}]
1036
+
1037
+ import { useState } from "react";
1038
+ import { Link } from "react-router-dom";
1039
+ import {
1040
+ Breadcrumb,
1041
+ BreadcrumbItem,
1042
+ BreadcrumbLink,
1043
+ BreadcrumbList,
1044
+ BreadcrumbPage,
1045
+ BreadcrumbSeparator,
1046
+ } from "@/components/ui/breadcrumb";
1047
+
1048
+ // ... other shadcn/ui component imports as needed
1049
+
1050
+ export default function PageComponent() {
1051
+ return (
1052
+ <>
1053
+ {/* Breadcrumb */}
1054
+ <div className="px-6 py-3 border-b bg-background">
1055
+ <Breadcrumb>
1056
+ <BreadcrumbList>
1057
+ <BreadcrumbItem>
1058
+ <BreadcrumbLink asChild>
1059
+ <Link to="/{{ROLE}}/home">Home</Link>
1060
+ </BreadcrumbLink>
1061
+ </BreadcrumbItem>
1062
+ <BreadcrumbSeparator />
1063
+ <BreadcrumbItem>
1064
+ <BreadcrumbPage>{{PAGE_TITLE}}</BreadcrumbPage>
1065
+ </BreadcrumbItem>
1066
+ </BreadcrumbList>
1067
+ </Breadcrumb>
1068
+ </div>
1069
+
1070
+ {/* Page Content */}
1071
+ <div className="flex-1 p-6">
1072
+ {/* ... page-specific content ... */}
1073
+ </div>
1074
+ </>
1075
+ );
1076
+ }
1077
+ ```
1078
+
1079
+ ### Breadcrumb for Sub-pages (detail / edit / create)
1080
+
1081
+ ```tsx
1082
+ <Breadcrumb>
1083
+ <BreadcrumbList>
1084
+ <BreadcrumbItem>
1085
+ <BreadcrumbLink asChild>
1086
+ <Link to="/{{ROLE}}/home">Home</Link>
1087
+ </BreadcrumbLink>
1088
+ </BreadcrumbItem>
1089
+ <BreadcrumbSeparator />
1090
+ <BreadcrumbItem>
1091
+ <BreadcrumbLink asChild>
1092
+ <Link to="/{{ROLE}}/{{MODULE}}">{{MODULE_NAME}}</Link>
1093
+ </BreadcrumbLink>
1094
+ </BreadcrumbItem>
1095
+ <BreadcrumbSeparator />
1096
+ <BreadcrumbItem>
1097
+ <BreadcrumbPage>{{PAGE_TITLE}}</BreadcrumbPage>
1098
+ </BreadcrumbItem>
1099
+ </BreadcrumbList>
1100
+ </Breadcrumb>
1101
+ ```
1102
+
1103
+ ---
1104
+
1105
+ ## Link Rules Inside Page Components
1106
+
1107
+ ### Navigation links (within the app)
1108
+
1109
+ Use React Router `<Link>` for in-app navigation:
1110
+
1111
+ ```tsx
1112
+ import { Link } from "react-router-dom";
1113
+
1114
+ <Link
1115
+ to="/{{ROLE}}/{{TARGET_PAGE}}"
1116
+ className="..."
1117
+ >
1118
+ View Details
1119
+ </Link>
1120
+ ```
1121
+
1122
+ ### Image links — MUST open in new tab
1123
+
1124
+ ```tsx
1125
+ <a href="https://placehold.co/800x600" target="_blank" rel="noopener noreferrer">
1126
+ View Image
1127
+ </a>
1128
+ ```
1129
+
1130
+ ### PDF / document links — MUST open in new tab
1131
+
1132
+ ```tsx
1133
+ import { FileText } from "lucide-react";
1134
+
1135
+ <a href="/reports/sample.pdf" target="_blank" rel="noopener noreferrer" className="flex items-center gap-2">
1136
+ <FileText className="h-4 w-4" />
1137
+ View PDF
1138
+ </a>
1139
+ ```
1140
+
1141
+ ### Action buttons in tables
1142
+
1143
+ ```tsx
1144
+ import { Link } from "react-router-dom";
1145
+ import { Button } from "@/components/ui/button";
1146
+ import {
1147
+ Dialog,
1148
+ DialogContent,
1149
+ DialogDescription,
1150
+ DialogFooter,
1151
+ DialogHeader,
1152
+ DialogTitle,
1153
+ DialogTrigger,
1154
+ } from "@/components/ui/dialog";
1155
+
1156
+ {/* View / Details → React Router Link */}
1157
+ <Button variant="outline" size="sm" asChild>
1158
+ <Link to="/{{ROLE}}/{{MODULE}}-detail">View</Link>
1159
+ </Button>
1160
+
1161
+ {/* Edit → React Router Link */}
1162
+ <Button variant="outline" size="sm" asChild>
1163
+ <Link to="/{{ROLE}}/{{MODULE}}-edit">Edit</Link>
1164
+ </Button>
1165
+
1166
+ {/* Delete → shadcn/ui Dialog */}
1167
+ <Dialog>
1168
+ <DialogTrigger asChild>
1169
+ <Button variant="outline" size="sm" className="text-destructive border-destructive/50">
1170
+ Delete
1171
+ </Button>
1172
+ </DialogTrigger>
1173
+ <DialogContent>
1174
+ <DialogHeader>
1175
+ <DialogTitle>Delete Record</DialogTitle>
1176
+ <DialogDescription>
1177
+ Are you sure you want to delete this record? This action cannot be undone.
1178
+ </DialogDescription>
1179
+ </DialogHeader>
1180
+ <DialogFooter>
1181
+ <Button variant="outline">Cancel</Button>
1182
+ <Button variant="destructive">Delete</Button>
1183
+ </DialogFooter>
1184
+ </DialogContent>
1185
+ </Dialog>
1186
+
1187
+ {/* Add New → React Router Link */}
1188
+ <Button asChild>
1189
+ <Link to="/{{ROLE}}/{{MODULE}}-create">
1190
+ + Add New
1191
+ </Link>
1192
+ </Button>
1193
+ ```
1194
+
1195
+ ### Tab navigation in detail pages
1196
+
1197
+ Use shadcn/ui `<Tabs>` for inline tabs:
1198
+
1199
+ ```tsx
1200
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
1201
+
1202
+ <Tabs defaultValue="overview">
1203
+ <TabsList>
1204
+ <TabsTrigger value="overview">Overview</TabsTrigger>
1205
+ <TabsTrigger value="documents">Documents</TabsTrigger>
1206
+ <TabsTrigger value="history">History</TabsTrigger>
1207
+ </TabsList>
1208
+ <TabsContent value="overview">
1209
+ {/* Overview content */}
1210
+ </TabsContent>
1211
+ <TabsContent value="documents">
1212
+ {/* Documents content */}
1213
+ </TabsContent>
1214
+ <TabsContent value="history">
1215
+ {/* History content */}
1216
+ </TabsContent>
1217
+ </Tabs>
1218
+ ```
1219
+
1220
+ If tabs are separate page files, use `<Link>` instead:
1221
+
1222
+ ```tsx
1223
+ import { Link, useLocation } from "react-router-dom";
1224
+ import { cn } from "@/lib/utils";
1225
+
1226
+ const tabs = [
1227
+ { label: "Overview", path: "/{{ROLE}}/{{MODULE}}-tab-overview" },
1228
+ { label: "Documents", path: "/{{ROLE}}/{{MODULE}}-tab-documents" },
1229
+ { label: "History", path: "/{{ROLE}}/{{MODULE}}-tab-history" },
1230
+ ];
1231
+
1232
+ const location = useLocation();
1233
+
1234
+ <div className="flex border-b mb-6">
1235
+ {tabs.map((tab) => (
1236
+ <Link
1237
+ key={tab.path}
1238
+ to={tab.path}
1239
+ className={cn(
1240
+ "px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors",
1241
+ location.pathname === tab.path
1242
+ ? "border-primary text-primary"
1243
+ : "border-transparent text-muted-foreground hover:text-foreground"
1244
+ )}
1245
+ >
1246
+ {tab.label}
1247
+ </Link>
1248
+ ))}
1249
+ </div>
1250
+ ```
1251
+
1252
+ ### Back / Cancel buttons
1253
+
1254
+ ```tsx
1255
+ import { Link } from "react-router-dom";
1256
+ import { Button } from "@/components/ui/button";
1257
+ import { ArrowLeft } from "lucide-react";
1258
+
1259
+ <Button variant="outline" asChild>
1260
+ <Link to="/{{ROLE}}/{{PARENT_MODULE}}">
1261
+ <ArrowLeft className="h-4 w-4 mr-2" />
1262
+ Back to List
1263
+ </Link>
1264
+ </Button>
1265
+ ```
1266
+
1267
+ ### Pagination
1268
+
1269
+ ```tsx
1270
+ import { useState } from "react";
1271
+ import { Button } from "@/components/ui/button";
1272
+ import {
1273
+ Select,
1274
+ SelectContent,
1275
+ SelectItem,
1276
+ SelectTrigger,
1277
+ SelectValue,
1278
+ } from "@/components/ui/select";
1279
+ import {
1280
+ Pagination,
1281
+ PaginationContent,
1282
+ PaginationEllipsis,
1283
+ PaginationItem,
1284
+ PaginationLink,
1285
+ PaginationNext,
1286
+ PaginationPrevious,
1287
+ } from "@/components/ui/pagination";
1288
+
1289
+ const [currentPage, setCurrentPage] = useState(1);
1290
+ const [pageSize, setPageSize] = useState(10);
1291
+ const totalItems = 47;
1292
+ const totalPages = Math.ceil(totalItems / pageSize);
1293
+
1294
+ <div className="flex items-center justify-between mt-4">
1295
+ <span className="text-sm text-muted-foreground">
1296
+ Showing {(currentPage - 1) * pageSize + 1}–{Math.min(currentPage * pageSize, totalItems)} of {totalItems} results
1297
+ </span>
1298
+
1299
+ <Pagination>
1300
+ <PaginationContent>
1301
+ <PaginationItem>
1302
+ <PaginationPrevious
1303
+ onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
1304
+ className={currentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}
1305
+ />
1306
+ </PaginationItem>
1307
+ {/* Page numbers — generate dynamically */}
1308
+ <PaginationItem>
1309
+ <PaginationLink
1310
+ isActive={currentPage === 1}
1311
+ onClick={() => setCurrentPage(1)}
1312
+ className="cursor-pointer"
1313
+ >
1314
+ 1
1315
+ </PaginationLink>
1316
+ </PaginationItem>
1317
+ {currentPage > 3 && (
1318
+ <PaginationItem>
1319
+ <PaginationEllipsis />
1320
+ </PaginationItem>
1321
+ )}
1322
+ {/* ... middle pages ... */}
1323
+ <PaginationItem>
1324
+ <PaginationNext
1325
+ onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
1326
+ className={currentPage === totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"}
1327
+ />
1328
+ </PaginationItem>
1329
+ </PaginationContent>
1330
+ </Pagination>
1331
+
1332
+ <Select value={String(pageSize)} onValueChange={(v) => { setPageSize(Number(v)); setCurrentPage(1); }}>
1333
+ <SelectTrigger className="w-20">
1334
+ <SelectValue />
1335
+ </SelectTrigger>
1336
+ <SelectContent>
1337
+ <SelectItem value="10">10</SelectItem>
1338
+ <SelectItem value="25">25</SelectItem>
1339
+ <SelectItem value="50">50</SelectItem>
1340
+ </SelectContent>
1341
+ </Select>
1342
+ </div>
1343
+ ```
1344
+
1345
+ ---
1346
+
1347
+ ## Common Pages Content
1348
+
1349
+ ### home.tsx page component
1350
+
1351
+ ```tsx
1352
+ import { Link } from "react-router-dom";
1353
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
1354
+ import {
1355
+ Table,
1356
+ TableBody,
1357
+ TableCell,
1358
+ TableHead,
1359
+ TableHeader,
1360
+ TableRow,
1361
+ } from "@/components/ui/table";
1362
+ import { Badge } from "@/components/ui/badge";
1363
+ import {
1364
+ Breadcrumb,
1365
+ BreadcrumbItem,
1366
+ BreadcrumbList,
1367
+ BreadcrumbPage,
1368
+ } from "@/components/ui/breadcrumb";
1369
+
1370
+ export default function Home() {
1371
+ return (
1372
+ <>
1373
+ <div className="px-6 py-3 border-b bg-background">
1374
+ <Breadcrumb>
1375
+ <BreadcrumbList>
1376
+ <BreadcrumbItem>
1377
+ <BreadcrumbPage>Home</BreadcrumbPage>
1378
+ </BreadcrumbItem>
1379
+ </BreadcrumbList>
1380
+ </Breadcrumb>
1381
+ </div>
1382
+ <div className="flex-1 p-6">
1383
+ <h1 className="text-2xl font-bold mb-2">Welcome back</h1>
1384
+ <p className="text-muted-foreground mb-6">{{APP_NAME}} — {{ROLE_DISPLAY}}</p>
1385
+
1386
+ {/* Summary stat cards — one per module */}
1387
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
1388
+ {/* Repeat: one Card per module */}
1389
+ <Card>
1390
+ <CardHeader className="pb-2">
1391
+ <CardTitle className="text-sm font-medium text-muted-foreground">
1392
+ Module Name
1393
+ </CardTitle>
1394
+ </CardHeader>
1395
+ <CardContent>
1396
+ <div className="text-2xl font-bold">142</div>
1397
+ <p className="text-xs text-muted-foreground">+12 this week</p>
1398
+ </CardContent>
1399
+ </Card>
1400
+ </div>
1401
+
1402
+ {/* Recent activity table */}
1403
+ <Card>
1404
+ <CardHeader>
1405
+ <CardTitle>Recent Activity</CardTitle>
1406
+ </CardHeader>
1407
+ <CardContent>
1408
+ <Table>
1409
+ <TableHeader>
1410
+ <TableRow>
1411
+ <TableHead>Action</TableHead>
1412
+ <TableHead>Module</TableHead>
1413
+ <TableHead>User</TableHead>
1414
+ <TableHead>Date</TableHead>
1415
+ </TableRow>
1416
+ </TableHeader>
1417
+ <TableBody>
1418
+ <TableRow>
1419
+ <TableCell><Badge>Created</Badge></TableCell>
1420
+ <TableCell>Module Name</TableCell>
1421
+ <TableCell>John Doe</TableCell>
1422
+ <TableCell>15 Aug 2025 10:30</TableCell>
1423
+ </TableRow>
1424
+ </TableBody>
1425
+ </Table>
1426
+ </CardContent>
1427
+ </Card>
1428
+ </div>
1429
+ </>
1430
+ );
1431
+ }
1432
+ ```
1433
+
1434
+ ### profile.tsx page component
1435
+
1436
+ User profile: `<Avatar>`, full name, email, role `<Badge>`, personal info `<Card>` (read-only),
1437
+ Edit `<Button>`.
1438
+
1439
+ ### account.tsx page component
1440
+
1441
+ Account settings: Change Password `<Card>` form, Notification Preferences `<Card>` with
1442
+ `<Switch>` toggles, Language/Locale `<Select>`, Session Management `<Card>` with `<Table>`.
1443
+
1444
+ ### notifications.tsx page component
1445
+
1446
+ Notifications list: shadcn/ui `<Tabs>` for All/Unread/Read filter, notification `<Card>` items
1447
+ with Lucide icon / title / message / timestamp / read-unread dot, Mark all as read `<Button>`,
1448
+ `<Pagination>` (MANDATORY).
1449
+
1450
+ ---
1451
+
1452
+ ## Quick-Start Instructions (append to output summary)
1453
+
1454
+ ```
1455
+ Quick Start
1456
+ ===========
1457
+ 1. cd {mockup folder} && npm install && npm run build (one-time build for the hub)
1458
+ 2. cd <root>/mockup
1459
+ 3. npm start (zero dependencies — no npm install required)
1460
+ 4. Open http://localhost:3000 in your browser
1461
+ - Landing page lists ALL applications and roles
1462
+ - This app's screen index: http://localhost:3000/{app_slug}
1463
+ 5. Change port: PORT=4000 npm start, or edit <root>/mockup/mockup.config.json
1464
+
1465
+ Design iteration (optional): cd {mockup folder} && npm run dev
1466
+ → http://localhost:5173/{app_slug}/
1467
+ ```