@atlashub/smartstack-cli 3.39.0 → 3.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.documentation/apex.html +644 -644
- package/.documentation/css/styles.css +2320 -2320
- package/.documentation/init.html +1377 -1377
- package/.documentation/js/app.js +780 -780
- package/.documentation/prd-json-v2.0.0.md +396 -396
- package/.documentation/testing-ba-e2e.md +462 -462
- package/config/default-config.json +95 -95
- package/config/mcp-defaults.json +62 -62
- package/config/settings.json +53 -53
- package/config/settings.local.example.json +16 -16
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +6 -4
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +115 -115
- package/scripts/extract-api-endpoints.ts +325 -325
- package/scripts/extract-business-rules.ts +440 -440
- package/scripts/generate-doc-with-mock-ui.ts +804 -804
- package/scripts/health-check.sh +168 -168
- package/scripts/postinstall.js +18 -18
- package/templates/agents/action.md +37 -37
- package/templates/agents/ba-reader.md +378 -378
- package/templates/agents/ba-writer.md +861 -861
- package/templates/agents/code-reviewer.md +163 -163
- package/templates/agents/db-reader.md +149 -149
- package/templates/agents/docs-context-reader.md +143 -143
- package/templates/agents/docs-sync-checker.md +122 -122
- package/templates/agents/efcore/conflicts.md +95 -84
- package/templates/agents/efcore/db-deploy.md +85 -74
- package/templates/agents/efcore/db-reset.md +96 -85
- package/templates/agents/efcore/db-seed.md +72 -61
- package/templates/agents/efcore/db-status.md +97 -86
- package/templates/agents/efcore/migration.md +197 -186
- package/templates/agents/efcore/rebase-snapshot.md +119 -108
- package/templates/agents/efcore/scan.md +103 -92
- package/templates/agents/efcore/squash.md +172 -161
- package/templates/agents/explore-codebase.md +66 -66
- package/templates/agents/explore-docs.md +98 -98
- package/templates/agents/fix-grammar.md +50 -50
- package/templates/agents/gitflow/abort.md +45 -45
- package/templates/agents/gitflow/cleanup.md +96 -96
- package/templates/agents/gitflow/commit.md +236 -236
- package/templates/agents/gitflow/exec.md +48 -48
- package/templates/agents/gitflow/finish.md +146 -146
- package/templates/agents/gitflow/init-clone.md +199 -199
- package/templates/agents/gitflow/init-detect.md +137 -137
- package/templates/agents/gitflow/init-validate.md +225 -225
- package/templates/agents/gitflow/init.md +340 -340
- package/templates/agents/gitflow/merge.md +145 -145
- package/templates/agents/gitflow/plan.md +42 -42
- package/templates/agents/gitflow/pr.md +191 -191
- package/templates/agents/gitflow/review.md +49 -49
- package/templates/agents/gitflow/start.md +147 -147
- package/templates/agents/gitflow/status.md +95 -95
- package/templates/agents/mcp-healthcheck.md +163 -163
- package/templates/agents/snipper.md +37 -37
- package/templates/agents/websearch.md +46 -46
- package/templates/hooks/appsettings-guard.sh +76 -76
- package/templates/hooks/docs-drift-check.md +96 -96
- package/templates/hooks/ef-migration-check.md +139 -139
- package/templates/hooks/hooks.json +58 -58
- package/templates/hooks/mcp-check.md +64 -64
- package/templates/hooks/ralph-mcp-logger.sh +46 -46
- package/templates/hooks/ralph-session-end.sh +69 -69
- package/templates/hooks/stop-hook.sh +177 -177
- package/templates/hooks/wsl-dotnet-cleanup.sh +24 -24
- package/templates/mcp-scaffolding/component.tsx.hbs +318 -318
- package/templates/mcp-scaffolding/controller.cs.hbs +192 -192
- package/templates/mcp-scaffolding/entity-extension.cs.hbs +239 -239
- package/templates/mcp-scaffolding/frontend/api-client.ts.hbs +116 -116
- package/templates/mcp-scaffolding/frontend/nav-routes.ts.hbs +133 -133
- package/templates/mcp-scaffolding/frontend/routes.tsx.hbs +126 -126
- package/templates/mcp-scaffolding/migrations/seed-roles.cs.hbs +261 -261
- package/templates/mcp-scaffolding/service-extension.cs.hbs +53 -53
- package/templates/mcp-scaffolding/tests/controller.test.cs.hbs +436 -436
- package/templates/mcp-scaffolding/tests/entity.test.cs.hbs +239 -239
- package/templates/mcp-scaffolding/tests/repository.test.cs.hbs +441 -441
- package/templates/mcp-scaffolding/tests/security.test.cs.hbs +442 -442
- package/templates/mcp-scaffolding/tests/service.test.cs.hbs +402 -402
- package/templates/mcp-scaffolding/tests/validator.test.cs.hbs +428 -428
- package/templates/project/DependencyInjection.Application.cs.template +25 -25
- package/templates/project/DependencyInjection.Infrastructure.cs.template +61 -61
- package/templates/project/DesignTimeExtensionsDbContextFactory.cs.template +70 -70
- package/templates/project/ExampleEntity.cs.template +116 -116
- package/templates/project/ExampleEntityConfiguration.cs.template +64 -64
- package/templates/project/ExampleService.cs.template +146 -146
- package/templates/project/ExtensionsDbContext.cs.template +41 -41
- package/templates/project/IExtensionsDbContext.cs.template +22 -22
- package/templates/project/Program.cs.template +47 -47
- package/templates/project/README.md +79 -79
- package/templates/project/api.ts.template +12 -12
- package/templates/project/appsettings.json.template +170 -170
- package/templates/project/claude-settings.json.template +5 -5
- package/templates/project/test-frontend/msw/handlers.ts +58 -58
- package/templates/project/test-frontend/msw/server.ts +25 -25
- package/templates/project/test-frontend/setup.ts +16 -16
- package/templates/project/test-frontend/test-utils.tsx +59 -59
- package/templates/project/test-frontend/vitest.config.ts +31 -31
- package/templates/ralph/README.md +93 -93
- package/templates/ralph/ralph.config.yaml +113 -113
- package/templates/scripts/setup-ralph-loop.sh +173 -173
- package/templates/skills/_resources/config-safety.md +61 -61
- package/templates/skills/_resources/context-digest-template.md +53 -53
- package/templates/skills/_resources/doc-context-cache.md +60 -60
- package/templates/skills/_resources/docs-manifest-schema.md +155 -155
- package/templates/skills/_resources/formatting-guide.md +124 -124
- package/templates/skills/_resources/mcp-validate-documentation-spec.md +181 -181
- package/templates/skills/_shared.md +228 -228
- package/templates/skills/admin/SKILL.md +48 -48
- package/templates/skills/ai-prompt/SKILL.md +107 -107
- package/templates/skills/ai-prompt/steps/step-00-init.md +47 -47
- package/templates/skills/ai-prompt/steps/step-01-implementation.md +122 -122
- package/templates/skills/apex/SKILL.md +168 -168
- package/templates/skills/apex/_shared.md +141 -141
- package/templates/skills/apex/references/agent-teams-protocol.md +164 -164
- package/templates/skills/apex/references/analysis-methods.md +141 -141
- package/templates/skills/apex/references/challenge-questions.md +145 -145
- package/templates/skills/apex/references/code-generation.md +412 -412
- package/templates/skills/apex/references/core-seed-data.md +1437 -1437
- package/templates/skills/apex/references/error-classification.md +144 -144
- package/templates/skills/apex/references/examine-build-validation.md +82 -82
- package/templates/skills/apex/references/execution-frontend-gates.md +177 -177
- package/templates/skills/apex/references/execution-frontend-patterns.md +105 -105
- package/templates/skills/apex/references/execution-layer1-rules.md +96 -96
- package/templates/skills/apex/references/initialization-challenge-flow.md +110 -110
- package/templates/skills/apex/references/planning-layer-mapping.md +151 -151
- package/templates/skills/apex/references/post-checks.md +1584 -1584
- package/templates/skills/apex/references/smartstack-api.md +1053 -1053
- package/templates/skills/apex/references/smartstack-frontend.md +1571 -1571
- package/templates/skills/apex/references/smartstack-layers.md +402 -402
- package/templates/skills/apex/steps/step-00-init.md +307 -307
- package/templates/skills/apex/steps/step-01-analyze.md +165 -165
- package/templates/skills/apex/steps/step-02-plan.md +144 -144
- package/templates/skills/apex/steps/step-03-execute.md +328 -328
- package/templates/skills/apex/steps/step-04-examine.md +263 -263
- package/templates/skills/apex/steps/step-05-deep-review.md +129 -129
- package/templates/skills/apex/steps/step-06-resolve.md +101 -101
- package/templates/skills/apex/steps/step-07-tests.md +238 -238
- package/templates/skills/apex/steps/step-08-run-tests.md +125 -125
- package/templates/skills/application/SKILL.md +4 -4
- package/templates/skills/application/references/application-roles-template.md +227 -227
- package/templates/skills/application/references/backend-controller-hierarchy.md +58 -58
- package/templates/skills/application/references/backend-entity-seeding.md +72 -72
- package/templates/skills/application/references/backend-seeding-and-dto-output.md +83 -83
- package/templates/skills/application/references/backend-table-prefix-mapping.md +79 -79
- package/templates/skills/application/references/backend-verification.md +88 -88
- package/templates/skills/application/references/frontend-i18n-and-output.md +67 -67
- package/templates/skills/application/references/frontend-route-naming.md +117 -117
- package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +107 -107
- package/templates/skills/application/references/frontend-verification.md +156 -156
- package/templates/skills/application/references/migration-checklist-troubleshooting.md +1 -1
- package/templates/skills/application/references/provider-template.md +177 -177
- package/templates/skills/application/references/roles-client-project-handling.md +55 -55
- package/templates/skills/application/references/roles-fallback-procedure.md +149 -149
- package/templates/skills/application/references/test-coverage-requirements.md +213 -213
- package/templates/skills/application/references/test-frontend.md +73 -73
- package/templates/skills/application/references/test-prerequisites.md +72 -72
- package/templates/skills/application/steps/step-05-frontend.md +176 -176
- package/templates/skills/application/steps/step-06-migration.md +193 -193
- package/templates/skills/application/steps/step-07-tests.md +356 -356
- package/templates/skills/application/steps/step-08-documentation.md +137 -137
- package/templates/skills/application/templates-backend.md +463 -463
- package/templates/skills/application/templates-frontend.md +685 -685
- package/templates/skills/application/templates-i18n.md +520 -520
- package/templates/skills/application/templates-seed.md +1096 -1096
- package/templates/skills/business-analyse/SKILL.md +327 -327
- package/templates/skills/business-analyse/_architecture.md +123 -123
- package/templates/skills/business-analyse/_elicitation.md +206 -206
- package/templates/skills/business-analyse/_module-loop.md +115 -115
- package/templates/skills/business-analyse/_shared.md +383 -383
- package/templates/skills/business-analyse/_suggestions.md +34 -34
- package/templates/skills/business-analyse/html/ba-interactive.html +4477 -4477
- package/templates/skills/business-analyse/html/build-html.js +77 -77
- package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +150 -150
- package/templates/skills/business-analyse/html/src/scripts/02-navigation.js +227 -227
- package/templates/skills/business-analyse/html/src/scripts/03-render-cadrage.js +199 -199
- package/templates/skills/business-analyse/html/src/scripts/04-render-modules.js +205 -205
- package/templates/skills/business-analyse/html/src/scripts/05-render-specs.js +647 -647
- package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +195 -195
- package/templates/skills/business-analyse/html/src/scripts/07-render-handoff.js +92 -92
- package/templates/skills/business-analyse/html/src/scripts/08-editing.js +135 -135
- package/templates/skills/business-analyse/html/src/scripts/09-export.js +168 -168
- package/templates/skills/business-analyse/html/src/scripts/10-comments.js +171 -171
- package/templates/skills/business-analyse/html/src/scripts/11-review-panel.js +166 -166
- package/templates/skills/business-analyse/html/src/styles/01-variables.css +38 -38
- package/templates/skills/business-analyse/html/src/styles/02-layout.css +101 -101
- package/templates/skills/business-analyse/html/src/styles/03-navigation.css +120 -120
- package/templates/skills/business-analyse/html/src/styles/04-cards.css +196 -196
- package/templates/skills/business-analyse/html/src/styles/05-modules.css +454 -454
- package/templates/skills/business-analyse/html/src/styles/06-wireframes.css +272 -272
- package/templates/skills/business-analyse/html/src/styles/07-comments.css +184 -184
- package/templates/skills/business-analyse/html/src/styles/08-review-panel.css +241 -241
- package/templates/skills/business-analyse/html/src/template.html +516 -516
- package/templates/skills/business-analyse/patterns/suggestion-catalog.md +546 -546
- package/templates/skills/business-analyse/questionnaire/00-application.md +160 -160
- package/templates/skills/business-analyse/questionnaire/00b-project.md +85 -85
- package/templates/skills/business-analyse/questionnaire/01-context.md +185 -185
- package/templates/skills/business-analyse/questionnaire/02-stakeholders.md +189 -189
- package/templates/skills/business-analyse/questionnaire/03-scope.md +164 -164
- package/templates/skills/business-analyse/questionnaire/04-data.md +88 -88
- package/templates/skills/business-analyse/questionnaire/05-integrations.md +58 -58
- package/templates/skills/business-analyse/questionnaire/06-security.md +68 -68
- package/templates/skills/business-analyse/questionnaire/07-ui.md +76 -76
- package/templates/skills/business-analyse/questionnaire/08-performance.md +42 -42
- package/templates/skills/business-analyse/questionnaire/09-constraints.md +45 -45
- package/templates/skills/business-analyse/questionnaire/10-documentation.md +43 -43
- package/templates/skills/business-analyse/questionnaire/11-data-lifecycle.md +59 -59
- package/templates/skills/business-analyse/questionnaire/12-migration.md +58 -58
- package/templates/skills/business-analyse/questionnaire/13-cross-module.md +69 -69
- package/templates/skills/business-analyse/questionnaire/14-risk-assumptions.md +135 -135
- package/templates/skills/business-analyse/questionnaire/15-success-metrics.md +136 -136
- package/templates/skills/business-analyse/questionnaire.md +337 -337
- package/templates/skills/business-analyse/react/application-viewer.md +242 -242
- package/templates/skills/business-analyse/react/components.md +551 -551
- package/templates/skills/business-analyse/react/i18n-template.md +306 -306
- package/templates/skills/business-analyse/references/acceptance-criteria.md +169 -169
- package/templates/skills/business-analyse/references/agent-module-prompt.md +362 -362
- package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +557 -557
- package/templates/skills/business-analyse/references/analysis-semantic-checks.md +190 -190
- package/templates/skills/business-analyse/references/cache-warming-strategy.md +566 -566
- package/templates/skills/business-analyse/references/cadrage-challenge-patterns.md +41 -41
- package/templates/skills/business-analyse/references/cadrage-coverage-matrix.md +74 -74
- package/templates/skills/business-analyse/references/cadrage-pre-analysis.md +115 -115
- package/templates/skills/business-analyse/references/cadrage-shared-modules.md +68 -69
- package/templates/skills/business-analyse/references/cadrage-structure-cards.md +85 -85
- package/templates/skills/business-analyse/references/compilation-structure-cards.md +297 -297
- package/templates/skills/business-analyse/references/consolidation-structural-checks.md +107 -107
- package/templates/skills/business-analyse/references/deploy-data-build.md +180 -180
- package/templates/skills/business-analyse/references/deploy-modes.md +118 -118
- package/templates/skills/business-analyse/references/detection-strategies.md +424 -424
- package/templates/skills/business-analyse/references/entity-architecture-decision.md +218 -218
- package/templates/skills/business-analyse/references/handoff-file-templates.md +120 -120
- package/templates/skills/business-analyse/references/handoff-mappings.md +81 -81
- package/templates/skills/business-analyse/references/handoff-seeddata-generation.md +312 -312
- package/templates/skills/business-analyse/references/html-data-mapping.md +299 -299
- package/templates/skills/business-analyse/references/init-schema-deployment.md +65 -65
- package/templates/skills/business-analyse/references/naming-conventions.md +243 -243
- package/templates/skills/business-analyse/references/prd-generation.md +258 -258
- package/templates/skills/business-analyse/references/review-data-mapping.md +363 -363
- package/templates/skills/business-analyse/references/robustness-checks.md +542 -542
- package/templates/skills/business-analyse/references/spec-auto-inference.md +111 -111
- package/templates/skills/business-analyse/references/team-orchestration.md +1022 -1022
- package/templates/skills/business-analyse/references/ui-dashboard-spec.md +85 -85
- package/templates/skills/business-analyse/references/ui-resource-cards.md +259 -259
- package/templates/skills/business-analyse/references/validate-incremental-html.md +121 -121
- package/templates/skills/business-analyse/references/validation-checklist.md +347 -347
- package/templates/skills/business-analyse/references/wireframe-svg-style-guide.md +335 -335
- package/templates/skills/business-analyse/schemas/application-schema.json +453 -453
- package/templates/skills/business-analyse/schemas/feature-schema.json +53 -53
- package/templates/skills/business-analyse/schemas/project-schema.json +485 -485
- package/templates/skills/business-analyse/schemas/sections/analysis-schema.json +201 -201
- package/templates/skills/business-analyse/schemas/sections/discovery-schema.json +82 -82
- package/templates/skills/business-analyse/schemas/sections/handoff-schema.json +80 -80
- package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +70 -70
- package/templates/skills/business-analyse/schemas/sections/specification-schema.json +547 -547
- package/templates/skills/business-analyse/schemas/sections/validation-schema.json +93 -93
- package/templates/skills/business-analyse/schemas/shared/common-defs.json +226 -226
- package/templates/skills/business-analyse/steps/step-00-init.md +575 -576
- package/templates/skills/business-analyse/steps/step-01-cadrage.md +767 -767
- package/templates/skills/business-analyse/steps/step-01b-applications.md +419 -419
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +387 -387
- package/templates/skills/business-analyse/steps/step-03a-data.md +16 -16
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +506 -506
- package/templates/skills/business-analyse/steps/step-03a2-analysis.md +252 -252
- package/templates/skills/business-analyse/steps/step-03b-ui.md +425 -425
- package/templates/skills/business-analyse/steps/step-03c-compile.md +611 -611
- package/templates/skills/business-analyse/steps/step-03d-validate.md +783 -783
- package/templates/skills/business-analyse/steps/step-04-consolidation.md +17 -17
- package/templates/skills/business-analyse/steps/step-04a-collect.md +415 -415
- package/templates/skills/business-analyse/steps/step-04b-analyze.md +163 -163
- package/templates/skills/business-analyse/steps/step-04c-decide.md +186 -186
- package/templates/skills/business-analyse/steps/step-05a-handoff.md +840 -840
- package/templates/skills/business-analyse/steps/step-05b-deploy.md +522 -522
- package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +703 -703
- package/templates/skills/business-analyse/steps/step-06-review.md +278 -278
- package/templates/skills/business-analyse/templates/tpl-frd.md +168 -168
- package/templates/skills/business-analyse/templates/tpl-handoff.md +186 -186
- package/templates/skills/business-analyse/templates/tpl-launch-displays.md +59 -59
- package/templates/skills/business-analyse/templates/tpl-progress.md +172 -172
- package/templates/skills/business-analyse/templates-frd.md +476 -476
- package/templates/skills/business-analyse/templates-react.md +574 -574
- package/templates/skills/cc-agent/SKILL.md +129 -129
- package/templates/skills/cc-agent/references/agent-behavior-patterns.md +95 -95
- package/templates/skills/cc-agent/references/agent-frontmatter.md +213 -213
- package/templates/skills/cc-agent/references/permission-modes.md +102 -102
- package/templates/skills/cc-agent/references/tools-reference.md +144 -144
- package/templates/skills/cc-agent/steps/step-00-init.md +134 -134
- package/templates/skills/cc-agent/steps/step-01-design.md +186 -186
- package/templates/skills/cc-agent/steps/step-02-generate.md +131 -131
- package/templates/skills/cc-agent/steps/step-03-validate.md +130 -130
- package/templates/skills/cc-agent/templates/agent-categorized.md +67 -67
- package/templates/skills/cc-agent/templates/agent-standalone.md +56 -56
- package/templates/skills/cc-agent/templates/agent-with-skills.md +94 -94
- package/templates/skills/cc-audit/SKILL.md +108 -108
- package/templates/skills/cc-audit/references/agent-checklist.md +91 -91
- package/templates/skills/cc-audit/references/hook-checklist.md +110 -110
- package/templates/skills/cc-audit/references/skill-checklist.md +70 -70
- package/templates/skills/cc-audit/steps/step-00-init.md +98 -98
- package/templates/skills/cc-audit/steps/step-01-scan.md +142 -142
- package/templates/skills/cc-audit/steps/step-02-analyze.md +158 -158
- package/templates/skills/cc-audit/steps/step-03-report.md +142 -142
- package/templates/skills/cc-skill/SKILL.md +134 -134
- package/templates/skills/cc-skill/references/best-practices.md +167 -167
- package/templates/skills/cc-skill/references/frontmatter-reference.md +182 -182
- package/templates/skills/cc-skill/references/skill-patterns.md +199 -199
- package/templates/skills/cc-skill/steps/step-00-init.md +119 -119
- package/templates/skills/cc-skill/steps/step-01-design.md +199 -199
- package/templates/skills/cc-skill/steps/step-02-generate.md +145 -145
- package/templates/skills/cc-skill/steps/step-03-steps.md +151 -151
- package/templates/skills/cc-skill/steps/step-04-validate.md +124 -124
- package/templates/skills/cc-skill/templates/skill-forked.md +85 -85
- package/templates/skills/cc-skill/templates/skill-progressive.md +102 -102
- package/templates/skills/cc-skill/templates/skill-simple.md +75 -75
- package/templates/skills/cc-skill/templates/step-template.md +82 -82
- package/templates/skills/check-version/SKILL.md +196 -196
- package/templates/skills/controller/SKILL.md +162 -162
- package/templates/skills/controller/postman-templates.md +614 -614
- package/templates/skills/controller/references/controller-code-templates.md +159 -159
- package/templates/skills/controller/references/mcp-scaffold-workflow.md +209 -209
- package/templates/skills/controller/references/permission-sync-templates.md +149 -149
- package/templates/skills/controller/steps/step-00-init.md +193 -191
- package/templates/skills/controller/steps/step-01-analyze.md +146 -146
- package/templates/skills/controller/steps/step-02-plan.md +176 -176
- package/templates/skills/controller/steps/step-03-generate.md +189 -189
- package/templates/skills/controller/steps/step-04-perms.md +80 -80
- package/templates/skills/controller/steps/step-05-validate.md +107 -107
- package/templates/skills/controller/templates.md +1555 -1555
- package/templates/skills/debug/SKILL.md +70 -70
- package/templates/skills/debug/references/team-protocol.md +232 -232
- package/templates/skills/debug/steps/step-00-init.md +57 -57
- package/templates/skills/debug/steps/step-01-analyze.md +219 -219
- package/templates/skills/debug/steps/step-02-resolve.md +85 -85
- package/templates/skills/documentation/SKILL.md +132 -132
- package/templates/skills/documentation/data-schema.md +227 -227
- package/templates/skills/documentation/steps/step-00-init.md +70 -70
- package/templates/skills/documentation/steps/step-01-scan.md +113 -113
- package/templates/skills/documentation/steps/step-02-generate.md +231 -231
- package/templates/skills/documentation/steps/step-03-validate.md +251 -238
- package/templates/skills/documentation/templates.md +662 -663
- package/templates/skills/efcore/SKILL.md +168 -167
- package/templates/skills/efcore/references/both-contexts.md +32 -32
- package/templates/skills/efcore/references/database-operations.md +67 -67
- package/templates/skills/efcore/references/destructive-operations.md +38 -38
- package/templates/skills/efcore/references/reset-operations.md +81 -81
- package/templates/skills/efcore/references/seed-methods.md +86 -86
- package/templates/skills/efcore/references/shared-init-functions.md +250 -250
- package/templates/skills/efcore/references/sql-objects-injection.md +61 -61
- package/templates/skills/efcore/references/troubleshooting.md +81 -81
- package/templates/skills/efcore/references/zero-downtime-patterns.md +227 -227
- package/templates/skills/efcore/steps/db/step-deploy.md +217 -217
- package/templates/skills/efcore/steps/db/step-reset.md +186 -186
- package/templates/skills/efcore/steps/db/step-seed.md +166 -166
- package/templates/skills/efcore/steps/db/step-status.md +173 -173
- package/templates/skills/efcore/steps/migration/step-00-init.md +102 -102
- package/templates/skills/efcore/steps/migration/step-01-check.md +164 -164
- package/templates/skills/efcore/steps/migration/step-02-create.md +160 -160
- package/templates/skills/efcore/steps/migration/step-03-validate.md +168 -168
- package/templates/skills/efcore/steps/rebase-snapshot/step-00-init.md +173 -173
- package/templates/skills/efcore/steps/rebase-snapshot/step-01-backup.md +100 -100
- package/templates/skills/efcore/steps/rebase-snapshot/step-02-fetch.md +115 -115
- package/templates/skills/efcore/steps/rebase-snapshot/step-03-create.md +112 -112
- package/templates/skills/efcore/steps/rebase-snapshot/step-04-validate.md +157 -157
- package/templates/skills/efcore/steps/shared/step-00-init.md +131 -131
- package/templates/skills/efcore/steps/squash/step-00-init.md +141 -141
- package/templates/skills/efcore/steps/squash/step-01-backup.md +120 -120
- package/templates/skills/efcore/steps/squash/step-02-fetch.md +168 -168
- package/templates/skills/efcore/steps/squash/step-03-create.md +184 -184
- package/templates/skills/efcore/steps/squash/step-04-validate.md +174 -174
- package/templates/skills/explore/SKILL.md +98 -98
- package/templates/skills/feature-full/SKILL.md +111 -111
- package/templates/skills/feature-full/steps/step-00-init.md +57 -57
- package/templates/skills/feature-full/steps/step-01-implementation.md +120 -120
- package/templates/skills/gitflow/SKILL.md +377 -377
- package/templates/skills/gitflow/_shared.md +620 -620
- package/templates/skills/gitflow/phases/abort.md +189 -189
- package/templates/skills/gitflow/phases/cleanup.md +234 -234
- package/templates/skills/gitflow/phases/status.md +192 -192
- package/templates/skills/gitflow/references/commit-message-generation.md +58 -58
- package/templates/skills/gitflow/references/commit-migration-validation.md +49 -49
- package/templates/skills/gitflow/references/finish-cleanup.md +55 -55
- package/templates/skills/gitflow/references/finish-version-bumping.md +45 -45
- package/templates/skills/gitflow/references/init-config-template.md +135 -135
- package/templates/skills/gitflow/references/init-environment-detection.md +41 -41
- package/templates/skills/gitflow/references/init-name-normalization.md +103 -103
- package/templates/skills/gitflow/references/init-questions.md +185 -185
- package/templates/skills/gitflow/references/init-structure-creation.md +75 -75
- package/templates/skills/gitflow/references/init-version-detection.md +21 -21
- package/templates/skills/gitflow/references/init-workspace-detection.md +43 -43
- package/templates/skills/gitflow/references/merge-ci-status.md +36 -36
- package/templates/skills/gitflow/references/merge-execution.md +62 -62
- package/templates/skills/gitflow/references/merge-pr-context.md +76 -76
- package/templates/skills/gitflow/references/plan-template.md +69 -69
- package/templates/skills/gitflow/references/pr-build-checks.md +60 -60
- package/templates/skills/gitflow/references/pr-generation.md +58 -58
- package/templates/skills/gitflow/references/start-branch-normalization.md +28 -28
- package/templates/skills/gitflow/references/start-efcore-preflight.md +70 -70
- package/templates/skills/gitflow/references/start-local-config.md +113 -113
- package/templates/skills/gitflow/references/start-worktree-creation.md +50 -50
- package/templates/skills/gitflow/references/sync-push-verify.md +44 -44
- package/templates/skills/gitflow/references/sync-rebase-conflicts.md +38 -38
- package/templates/skills/gitflow/steps/step-commit.md +199 -199
- package/templates/skills/gitflow/steps/step-finish.md +147 -147
- package/templates/skills/gitflow/steps/step-init.md +190 -190
- package/templates/skills/gitflow/steps/step-merge.md +85 -85
- package/templates/skills/gitflow/steps/step-plan.md +151 -151
- package/templates/skills/gitflow/steps/step-pr.md +199 -199
- package/templates/skills/gitflow/steps/step-start.md +195 -195
- package/templates/skills/gitflow/steps/step-sync.md +161 -161
- package/templates/skills/gitflow/templates/config.json +72 -72
- package/templates/skills/mcp/SKILL.md +62 -62
- package/templates/skills/mcp/steps/step-01-healthcheck.md +108 -108
- package/templates/skills/mcp/steps/step-02-tools.md +73 -73
- package/templates/skills/notification/SKILL.md +173 -173
- package/templates/skills/quick-search/SKILL.md +99 -99
- package/templates/skills/ralph-loop/SKILL.md +234 -234
- package/templates/skills/ralph-loop/references/category-completeness.md +185 -185
- package/templates/skills/ralph-loop/references/category-rules.md +96 -96
- package/templates/skills/ralph-loop/references/compact-loop.md +300 -300
- package/templates/skills/ralph-loop/references/init-resume-recovery.md +127 -127
- package/templates/skills/ralph-loop/references/module-transition.md +151 -151
- package/templates/skills/ralph-loop/references/multi-module-queue.md +171 -171
- package/templates/skills/ralph-loop/references/parallel-execution.md +246 -246
- package/templates/skills/ralph-loop/references/section-splitting.md +439 -439
- package/templates/skills/ralph-loop/references/task-transform-legacy.md +256 -256
- package/templates/skills/ralph-loop/references/team-orchestration.md +547 -547
- package/templates/skills/ralph-loop/steps/step-00-init.md +150 -150
- package/templates/skills/ralph-loop/steps/step-01-task.md +174 -174
- package/templates/skills/ralph-loop/steps/step-02-execute.md +177 -177
- package/templates/skills/ralph-loop/steps/step-03-commit.md +92 -92
- package/templates/skills/ralph-loop/steps/step-04-check.md +207 -207
- package/templates/skills/ralph-loop/steps/step-05-report.md +175 -175
- package/templates/skills/refactor/SKILL.md +56 -56
- package/templates/skills/refactor/steps/step-01-discover.md +60 -60
- package/templates/skills/refactor/steps/step-02-execute.md +67 -67
- package/templates/skills/review-code/SKILL.md +95 -94
- package/templates/skills/review-code/references/clean-code-principles.md +292 -292
- package/templates/skills/review-code/references/code-quality-metrics.md +174 -174
- package/templates/skills/review-code/references/feedback-patterns.md +149 -149
- package/templates/skills/review-code/references/owasp-api-top10.md +243 -243
- package/templates/skills/review-code/references/security-checklist.md +212 -212
- package/templates/skills/review-code/steps/step-01-smartstack.md +96 -96
- package/templates/skills/review-code/steps/step-02-detailed-review.md +80 -80
- package/templates/skills/review-code/steps/step-03-react.md +44 -44
- package/templates/skills/ui-components/SKILL.md +137 -137
- package/templates/skills/ui-components/accessibility.md +170 -170
- package/templates/skills/ui-components/patterns/dashboard-chart.md +327 -327
- package/templates/skills/ui-components/patterns/data-table.md +39 -39
- package/templates/skills/ui-components/patterns/entity-card.md +77 -77
- package/templates/skills/ui-components/patterns/grid-layout.md +91 -91
- package/templates/skills/ui-components/patterns/kanban.md +43 -43
- package/templates/skills/ui-components/responsive-guidelines.md +278 -278
- package/templates/skills/ui-components/style-guide.md +113 -113
- package/templates/skills/utils/SKILL.md +44 -44
- package/templates/skills/utils/subcommands/test-web-config.md +152 -152
- package/templates/skills/utils/subcommands/test-web.md +123 -123
- package/templates/skills/validate/SKILL.md +181 -181
- package/templates/skills/validate-feature/SKILL.md +101 -101
- package/templates/skills/validate-feature/references/api-smoke-tests.md +140 -140
- package/templates/skills/validate-feature/references/db-validation-checks.md +180 -180
- package/templates/skills/validate-feature/steps/step-00-dependencies.md +121 -121
- package/templates/skills/validate-feature/steps/step-01-compile.md +39 -39
- package/templates/skills/validate-feature/steps/step-02-unit-tests.md +45 -45
- package/templates/skills/validate-feature/steps/step-03-integration-tests.md +53 -53
- package/templates/skills/validate-feature/steps/step-04-api-smoke.md +94 -94
- package/templates/skills/validate-feature/steps/step-05-db-validation.md +149 -149
- package/templates/skills/workflow/SKILL.md +127 -127
- package/templates/skills/workflow/steps/step-00-init.md +57 -57
- package/templates/skills/workflow/steps/step-01-implementation.md +84 -84
- package/templates/test-web/api-health.json +38 -38
- package/templates/test-web/minimal.json +19 -19
- package/templates/test-web/npm-package.json +46 -46
- package/templates/test-web/seo-check.json +54 -54
|
@@ -1,685 +1,685 @@
|
|
|
1
|
-
# Templates Frontend - Application Skill
|
|
2
|
-
|
|
3
|
-
> These templates generate React/TypeScript code for new applications/modules.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## FRONTEND ARCHITECTURE
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
web/smartstack-web/src/
|
|
11
|
-
├── pages/$APPLICATION/$MODULE/
|
|
12
|
-
│ ├── $MODULE_PASCALPage.tsx # Main page (list)
|
|
13
|
-
│ ├── $MODULE_PASCALDetailPage.tsx # Detail page
|
|
14
|
-
│ └── Create$MODULE_PASCALPage.tsx # Create page
|
|
15
|
-
├── components/$APPLICATION/$MODULE/
|
|
16
|
-
│ ├── $MODULE_PASCALListView.tsx # Reusable list component
|
|
17
|
-
│ ├── $MODULE_PASCALForm.tsx # CRUD form
|
|
18
|
-
│ └── $MODULE_PASCALFilters.tsx # Filters
|
|
19
|
-
├── components/common/
|
|
20
|
-
│ └── (shared components)
|
|
21
|
-
├── hooks/
|
|
22
|
-
│ ├── use$MODULE_PASCALPreferences.ts # Preferences hook
|
|
23
|
-
│ └── use$MODULE_PASCAL.ts # API hook
|
|
24
|
-
├── services/api/
|
|
25
|
-
│ └── $moduleApi.ts # API service
|
|
26
|
-
└── i18n/locales/
|
|
27
|
-
├── fr/$module.json
|
|
28
|
-
├── en/$module.json
|
|
29
|
-
├── it/$module.json
|
|
30
|
-
└── de/$module.json
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## ROUTE PATH VARIABLES
|
|
36
|
-
|
|
37
|
-
> **All route path segments MUST use kebab-case to match the navigation seed data.**
|
|
38
|
-
|
|
39
|
-
| Variable | Description | Example |
|
|
40
|
-
|----------|-------------|---------|
|
|
41
|
-
| `$APPLICATION_KEBAB` | kebab-case of application code | `human-resources` |
|
|
42
|
-
| `$MODULE_KEBAB` | kebab-case of module code | `time-management` |
|
|
43
|
-
|
|
44
|
-
**Transformation rule:** `PascalCase` → `kebab-case` via regex `([a-z])([A-Z])` → `$1-$2` + `toLowerCase()`
|
|
45
|
-
|
|
46
|
-
Examples:
|
|
47
|
-
- `HumanResources` → `human-resources`
|
|
48
|
-
- `TimeManagement` → `time-management`
|
|
49
|
-
- `Employees` → `employees` (single word, no change needed)
|
|
50
|
-
|
|
51
|
-
> **FORBIDDEN:** Using raw `$APPLICATION` or `$MODULE` in route paths — they may produce non-kebab-case URLs (e.g., `humanresources` instead of `human-resources`). Always use `$APPLICATION_KEBAB` and `$MODULE_KEBAB` for route paths.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## TEMPLATE: MAIN PAGE
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
// pages/$APPLICATION/$MODULE/$MODULE_PASCALPage.tsx
|
|
59
|
-
|
|
60
|
-
import { useTranslation } from 'react-i18next';
|
|
61
|
-
import { $MODULE_PASCALListView } from '@/components/$APPLICATION/$MODULE/$MODULE_PASCALListView';
|
|
62
|
-
|
|
63
|
-
export function $MODULE_PASCALPage() {
|
|
64
|
-
const { t } = useTranslation(['$module', 'common']);
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<$MODULE_PASCALListView
|
|
68
|
-
title={t('$module:title')}
|
|
69
|
-
subtitle={t('$module:subtitle')}
|
|
70
|
-
createPath="new"
|
|
71
|
-
/>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## TEMPLATE: LIST VIEW (Reusable component)
|
|
79
|
-
|
|
80
|
-
```tsx
|
|
81
|
-
// components/$APPLICATION/$MODULE/$MODULE_PASCALListView.tsx
|
|
82
|
-
|
|
83
|
-
import { useState, useEffect } from 'react';
|
|
84
|
-
import { useNavigate } from 'react-router-dom';
|
|
85
|
-
import { useTranslation } from 'react-i18next';
|
|
86
|
-
import { Plus, Search, MoreVertical, Pencil, Trash2, Check, AlertCircle } from 'lucide-react';
|
|
87
|
-
import { api } from '@/services/api/apiClient';
|
|
88
|
-
import { use$MODULE_PASCALPreferences } from '@/hooks/use$MODULE_PASCALPreferences';
|
|
89
|
-
import { Pagination } from '@/components/common/Pagination';
|
|
90
|
-
import { ColumnSelector } from '@/components/common/ColumnSelector';
|
|
91
|
-
import { ViewToggle } from '@/components/ui/DataView';
|
|
92
|
-
import { EntityCard } from '@/components/ui/EntityCard'; // ⚠️ MANDATORY for Grid view
|
|
93
|
-
|
|
94
|
-
interface $ENTITY_PASCALDto {
|
|
95
|
-
id: string;
|
|
96
|
-
name: string;
|
|
97
|
-
description: string | null;
|
|
98
|
-
isActive: boolean;
|
|
99
|
-
createdAt: string;
|
|
100
|
-
updatedAt: string | null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
interface PaginatedResult {
|
|
104
|
-
items: $ENTITY_PASCALDto[];
|
|
105
|
-
totalCount: number;
|
|
106
|
-
page: number;
|
|
107
|
-
pageSize: number;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
interface $MODULE_PASCALListViewProps {
|
|
111
|
-
title: string;
|
|
112
|
-
subtitle?: string;
|
|
113
|
-
createPath?: string;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const DEFAULT_COLUMNS = ['name', 'description', 'isActive', 'createdAt'];
|
|
117
|
-
|
|
118
|
-
export function $MODULE_PASCALListView({
|
|
119
|
-
title,
|
|
120
|
-
subtitle,
|
|
121
|
-
createPath,
|
|
122
|
-
}: $MODULE_PASCALListViewProps) {
|
|
123
|
-
const { t } = useTranslation(['$module', 'common']);
|
|
124
|
-
const navigate = useNavigate();
|
|
125
|
-
const {
|
|
126
|
-
pageSize,
|
|
127
|
-
sortColumn,
|
|
128
|
-
sortDirection,
|
|
129
|
-
visibleColumns,
|
|
130
|
-
viewMode,
|
|
131
|
-
setPageSize,
|
|
132
|
-
setSortColumn,
|
|
133
|
-
setSortDirection,
|
|
134
|
-
setVisibleColumns,
|
|
135
|
-
setViewMode,
|
|
136
|
-
} = use$MODULE_PASCALPreferences();
|
|
137
|
-
|
|
138
|
-
const [data, setData] = useState<PaginatedResult | null>(null);
|
|
139
|
-
const [loading, setLoading] = useState(true);
|
|
140
|
-
const [error, setError] = useState<string | null>(null);
|
|
141
|
-
const [page, setPage] = useState(1);
|
|
142
|
-
const [search, setSearchTerm] = useState('');
|
|
143
|
-
|
|
144
|
-
useEffect(() => {
|
|
145
|
-
loadData();
|
|
146
|
-
}, [page, pageSize, sortColumn, sortDirection, search]);
|
|
147
|
-
|
|
148
|
-
const loadData = async () => {
|
|
149
|
-
try {
|
|
150
|
-
setLoading(true);
|
|
151
|
-
setError(null);
|
|
152
|
-
const params = new URLSearchParams({
|
|
153
|
-
page: page.toString(),
|
|
154
|
-
pageSize: pageSize.toString(),
|
|
155
|
-
sortColumn,
|
|
156
|
-
sortDirection,
|
|
157
|
-
...(search && { search }),
|
|
158
|
-
});
|
|
159
|
-
const result = await api.get<PaginatedResult>(`/api/$module?${params}`);
|
|
160
|
-
setData(result);
|
|
161
|
-
} catch (err) {
|
|
162
|
-
setError(t('common:errors.loadFailed'));
|
|
163
|
-
console.error('Failed to load $module:', err);
|
|
164
|
-
} finally {
|
|
165
|
-
setLoading(false);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
const handleSort = (column: string) => {
|
|
170
|
-
if (sortColumn === column) {
|
|
171
|
-
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
|
172
|
-
} else {
|
|
173
|
-
setSortColumn(column);
|
|
174
|
-
setSortDirection('asc');
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
const handleDelete = async (id: string) => {
|
|
179
|
-
if (!confirm(t('common:confirmDelete'))) return;
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
await api.delete(`/api/$module/${id}`);
|
|
183
|
-
loadData();
|
|
184
|
-
} catch (err) {
|
|
185
|
-
console.error('Failed to delete:', err);
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const columns = [
|
|
190
|
-
{ key: 'name', label: t('$module:columns.name'), sortable: true },
|
|
191
|
-
{ key: 'description', label: t('$module:columns.description'), sortable: false },
|
|
192
|
-
{ key: 'isActive', label: t('$module:columns.status'), sortable: true },
|
|
193
|
-
{ key: 'createdAt', label: t('$module:columns.createdAt'), sortable: true },
|
|
194
|
-
];
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
<div className="space-y-6">
|
|
198
|
-
{/* Header */}
|
|
199
|
-
<div className="flex items-center justify-between">
|
|
200
|
-
<div>
|
|
201
|
-
<h1 className="text-2xl font-bold text-[var(--text-primary)]">{title}</h1>
|
|
202
|
-
{subtitle && (
|
|
203
|
-
<p className="text-[var(--text-secondary)] mt-1">{subtitle}</p>
|
|
204
|
-
)}
|
|
205
|
-
</div>
|
|
206
|
-
{createPath && (
|
|
207
|
-
<button
|
|
208
|
-
onClick={() => navigate(createPath)}
|
|
209
|
-
className="flex items-center gap-2 px-4 py-2 bg-[var(--color-accent-500)] hover:bg-[var(--color-accent-600)] text-white font-medium transition-colors"
|
|
210
|
-
style={{ borderRadius: 'var(--radius-button)' }}
|
|
211
|
-
>
|
|
212
|
-
<Plus className="w-4 h-4" />
|
|
213
|
-
{t('common:actions.create')}
|
|
214
|
-
</button>
|
|
215
|
-
)}
|
|
216
|
-
</div>
|
|
217
|
-
|
|
218
|
-
{/* Toolbar */}
|
|
219
|
-
<div className="flex items-center justify-between gap-4">
|
|
220
|
-
<div className="relative flex-1 max-w-md">
|
|
221
|
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--text-muted)]" />
|
|
222
|
-
<input
|
|
223
|
-
type="text"
|
|
224
|
-
placeholder={t('common:search.placeholder')}
|
|
225
|
-
value={search}
|
|
226
|
-
onChange={(e) => setSearchTerm(e.target.value)}
|
|
227
|
-
className="w-full pl-10 pr-4 py-2 bg-[var(--bg-primary)] border border-[var(--border-color)] text-sm focus:outline-none focus:border-[var(--color-accent-500)]"
|
|
228
|
-
style={{ borderRadius: 'var(--radius-input)' }}
|
|
229
|
-
/>
|
|
230
|
-
</div>
|
|
231
|
-
<div className="flex items-center gap-2">
|
|
232
|
-
<ColumnSelector
|
|
233
|
-
columns={columns}
|
|
234
|
-
visibleColumns={visibleColumns}
|
|
235
|
-
onChange={setVisibleColumns}
|
|
236
|
-
/>
|
|
237
|
-
<ViewToggle value={viewMode} onChange={setViewMode} />
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
|
|
241
|
-
{/* Content */}
|
|
242
|
-
{loading ? (
|
|
243
|
-
<div className="flex items-center justify-center py-12">
|
|
244
|
-
<div className="animate-spin h-8 w-8 border-4 border-[var(--color-accent-500)] border-t-transparent rounded-full" />
|
|
245
|
-
</div>
|
|
246
|
-
) : error ? (
|
|
247
|
-
<div className="p-4 bg-[var(--error-bg)] border border-[var(--error-border)] rounded-[var(--radius-card)] flex items-center gap-3">
|
|
248
|
-
<AlertCircle className="w-5 h-5 text-[var(--error-dot)] flex-shrink-0" />
|
|
249
|
-
<span className="text-[var(--error-text)]">{error}</span>
|
|
250
|
-
<button onClick={() => fetchData()} className="ml-auto text-sm text-[var(--error-text)] hover:underline">
|
|
251
|
-
{t('common:actions.retry')}
|
|
252
|
-
</button>
|
|
253
|
-
</div>
|
|
254
|
-
) : data?.items.length === 0 ? (
|
|
255
|
-
<div className="text-center py-16">
|
|
256
|
-
<div className="w-16 h-16 mx-auto rounded-full bg-[var(--bg-secondary)] flex items-center justify-center mb-4">
|
|
257
|
-
<Search className="w-8 h-8 text-[var(--text-tertiary)]" />
|
|
258
|
-
</div>
|
|
259
|
-
<p className="text-[var(--text-secondary)]">{t('$module:list.empty')}</p>
|
|
260
|
-
</div>
|
|
261
|
-
) : viewMode === 'list' ? (
|
|
262
|
-
<div className="bg-[var(--bg-card)] border border-[var(--item-color-border)] overflow-hidden" style={{ borderRadius: 'var(--radius-card)' }}>
|
|
263
|
-
<table className="w-full">
|
|
264
|
-
<thead>
|
|
265
|
-
<tr className="border-b border-[var(--item-color-border)]">
|
|
266
|
-
{columns.filter(col => visibleColumns.includes(col.key)).map((col) => (
|
|
267
|
-
<th
|
|
268
|
-
key={col.key}
|
|
269
|
-
className={`px-4 py-3 text-left text-sm font-medium text-[var(--text-secondary)] ${
|
|
270
|
-
col.sortable ? 'cursor-pointer hover:text-[var(--text-primary)]' : ''
|
|
271
|
-
}`}
|
|
272
|
-
onClick={() => col.sortable && handleSort(col.key)}
|
|
273
|
-
>
|
|
274
|
-
{col.label}
|
|
275
|
-
{sortColumn === col.key && (
|
|
276
|
-
<span className="ml-1">{sortDirection === 'asc' ? '↑' : '↓'}</span>
|
|
277
|
-
)}
|
|
278
|
-
</th>
|
|
279
|
-
))}
|
|
280
|
-
<th className="px-4 py-3 text-right text-sm font-medium text-[var(--text-secondary)]">
|
|
281
|
-
{t('common:actions.title')}
|
|
282
|
-
</th>
|
|
283
|
-
</tr>
|
|
284
|
-
</thead>
|
|
285
|
-
<tbody>
|
|
286
|
-
{data?.items.map((item) => (
|
|
287
|
-
<tr
|
|
288
|
-
key={item.id}
|
|
289
|
-
className="border-b border-[var(--item-color-border)] hover:bg-[var(--bg-hover)] cursor-pointer"
|
|
290
|
-
onClick={() => navigate(item.id)}
|
|
291
|
-
>
|
|
292
|
-
{visibleColumns.includes('name') && (
|
|
293
|
-
<td className="px-4 py-3 text-sm text-[var(--text-primary)] font-medium">
|
|
294
|
-
{item.name}
|
|
295
|
-
</td>
|
|
296
|
-
)}
|
|
297
|
-
{visibleColumns.includes('description') && (
|
|
298
|
-
<td className="px-4 py-3 text-sm text-[var(--text-secondary)]">
|
|
299
|
-
{item.description || '-'}
|
|
300
|
-
</td>
|
|
301
|
-
)}
|
|
302
|
-
{visibleColumns.includes('isActive') && (
|
|
303
|
-
<td className="px-4 py-3">
|
|
304
|
-
<span
|
|
305
|
-
className={`inline-flex items-center px-2 py-1 text-xs font-medium ${
|
|
306
|
-
item.isActive
|
|
307
|
-
? 'bg-[var(--success-bg)] text-[var(--success-text)]'
|
|
308
|
-
: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'
|
|
309
|
-
}`}
|
|
310
|
-
style={{ borderRadius: 'var(--radius-badge)' }}
|
|
311
|
-
>
|
|
312
|
-
{item.isActive ? t('common:status.active') : t('common:status.inactive')}
|
|
313
|
-
</span>
|
|
314
|
-
</td>
|
|
315
|
-
)}
|
|
316
|
-
{visibleColumns.includes('createdAt') && (
|
|
317
|
-
<td className="px-4 py-3 text-sm text-[var(--text-secondary)]">
|
|
318
|
-
{new Date(item.createdAt).toLocaleDateString()}
|
|
319
|
-
</td>
|
|
320
|
-
)}
|
|
321
|
-
<td className="px-4 py-3 text-right">
|
|
322
|
-
<div className="flex items-center justify-end gap-2">
|
|
323
|
-
<button
|
|
324
|
-
onClick={(e) => {
|
|
325
|
-
e.stopPropagation();
|
|
326
|
-
navigate(`${item.id}/edit`);
|
|
327
|
-
}}
|
|
328
|
-
className="p-1 hover:bg-[var(--bg-tertiary)]"
|
|
329
|
-
style={{ borderRadius: 'var(--radius-button)' }}
|
|
330
|
-
>
|
|
331
|
-
<Pencil className="w-4 h-4 text-[var(--text-secondary)]" />
|
|
332
|
-
</button>
|
|
333
|
-
<button
|
|
334
|
-
onClick={(e) => {
|
|
335
|
-
e.stopPropagation();
|
|
336
|
-
handleDelete(item.id);
|
|
337
|
-
}}
|
|
338
|
-
className="p-1 hover:bg-[var(--error-bg)]"
|
|
339
|
-
style={{ borderRadius: 'var(--radius-button)' }}
|
|
340
|
-
>
|
|
341
|
-
<Trash2 className="w-4 h-4 text-[var(--error-text)]" />
|
|
342
|
-
</button>
|
|
343
|
-
</div>
|
|
344
|
-
</td>
|
|
345
|
-
</tr>
|
|
346
|
-
))}
|
|
347
|
-
</tbody>
|
|
348
|
-
</table>
|
|
349
|
-
</div>
|
|
350
|
-
) : (
|
|
351
|
-
/* Grid view - ⚠️ MANDATORY: use EntityCard */
|
|
352
|
-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
353
|
-
{data?.items.map((item) => (
|
|
354
|
-
<EntityCard
|
|
355
|
-
key={item.id}
|
|
356
|
-
avatar={{ letter: item.name[0].toUpperCase(), color: 'var(--color-accent-500)' }}
|
|
357
|
-
title={item.name}
|
|
358
|
-
subtitle={item.code}
|
|
359
|
-
description={item.description}
|
|
360
|
-
badge={item.isActive ? {
|
|
361
|
-
icon: Check,
|
|
362
|
-
tooltip: t('common:status.active'),
|
|
363
|
-
color: 'var(--success-text)'
|
|
364
|
-
} : undefined}
|
|
365
|
-
stats={`${t('common:createdAt')}: ${new Date(item.createdAt).toLocaleDateString()}`}
|
|
366
|
-
onClick={() => navigate(item.id)}
|
|
367
|
-
actions={[
|
|
368
|
-
{ label: t('common:actions.view'), onClick: () => navigate(item.id), variant: 'primary' },
|
|
369
|
-
]}
|
|
370
|
-
/>
|
|
371
|
-
))}
|
|
372
|
-
</div>
|
|
373
|
-
)}
|
|
374
|
-
|
|
375
|
-
{/* Pagination */}
|
|
376
|
-
{data && (
|
|
377
|
-
<Pagination
|
|
378
|
-
page={data.page}
|
|
379
|
-
pageSize={data.pageSize}
|
|
380
|
-
totalCount={data.totalCount}
|
|
381
|
-
onPageChange={setPage}
|
|
382
|
-
onPageSizeChange={setPageSize}
|
|
383
|
-
/>
|
|
384
|
-
)}
|
|
385
|
-
</div>
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
---
|
|
391
|
-
|
|
392
|
-
## TEMPLATE: PREFERENCES HOOK
|
|
393
|
-
|
|
394
|
-
```tsx
|
|
395
|
-
// hooks/use$MODULE_PASCALPreferences.ts
|
|
396
|
-
|
|
397
|
-
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
|
398
|
-
|
|
399
|
-
const DEFAULT_COLUMNS = ['name', 'description', 'isActive', 'createdAt'];
|
|
400
|
-
|
|
401
|
-
export function use$MODULE_PASCALPreferences() {
|
|
402
|
-
const { preferences, updatePreference } = useUserPreferences();
|
|
403
|
-
const modulePrefs = preferences.$module || {};
|
|
404
|
-
|
|
405
|
-
return {
|
|
406
|
-
// Getters with defaults
|
|
407
|
-
pageSize: modulePrefs.pageSize ?? 10,
|
|
408
|
-
sortColumn: modulePrefs.sortColumn ?? 'createdAt',
|
|
409
|
-
sortDirection: modulePrefs.sortDirection ?? 'desc',
|
|
410
|
-
filters: modulePrefs.filters ?? {},
|
|
411
|
-
visibleColumns: modulePrefs.visibleColumns ?? DEFAULT_COLUMNS,
|
|
412
|
-
viewMode: modulePrefs.viewMode ?? 'list',
|
|
413
|
-
|
|
414
|
-
// Setters
|
|
415
|
-
setPageSize: (size: number) =>
|
|
416
|
-
updatePreference('$module.pageSize', size),
|
|
417
|
-
setSortColumn: (col: string) =>
|
|
418
|
-
updatePreference('$module.sortColumn', col),
|
|
419
|
-
setSortDirection: (dir: 'asc' | 'desc') =>
|
|
420
|
-
updatePreference('$module.sortDirection', dir),
|
|
421
|
-
setFilters: (filters: Record<string, unknown>) =>
|
|
422
|
-
updatePreference('$module.filters', filters),
|
|
423
|
-
setVisibleColumns: (cols: string[]) =>
|
|
424
|
-
updatePreference('$module.visibleColumns', cols),
|
|
425
|
-
setViewMode: (mode: 'list' | 'grid') =>
|
|
426
|
-
updatePreference('$module.viewMode', mode),
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
---
|
|
432
|
-
|
|
433
|
-
## TEMPLATE: SERVICE API
|
|
434
|
-
|
|
435
|
-
```tsx
|
|
436
|
-
// services/api/$moduleApi.ts
|
|
437
|
-
|
|
438
|
-
import { api } from './apiClient';
|
|
439
|
-
|
|
440
|
-
export interface $ENTITY_PASCALDto {
|
|
441
|
-
id: string;
|
|
442
|
-
name: string;
|
|
443
|
-
description: string | null;
|
|
444
|
-
isActive: boolean;
|
|
445
|
-
createdAt: string;
|
|
446
|
-
updatedAt: string | null;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
export interface Create$ENTITY_PASCALRequest {
|
|
450
|
-
name: string;
|
|
451
|
-
description?: string;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
export interface Update$ENTITY_PASCALRequest {
|
|
455
|
-
name: string;
|
|
456
|
-
description?: string;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
export interface PaginatedResult<T> {
|
|
460
|
-
items: T[];
|
|
461
|
-
totalCount: number;
|
|
462
|
-
page: number;
|
|
463
|
-
pageSize: number;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
export interface PaginationParams {
|
|
467
|
-
page?: number;
|
|
468
|
-
pageSize?: number;
|
|
469
|
-
search?: string;
|
|
470
|
-
sortColumn?: string;
|
|
471
|
-
sortDirection?: 'asc' | 'desc';
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
export const $moduleApi = {
|
|
475
|
-
getAll: async (params: PaginationParams = {}): Promise<PaginatedResult<$ENTITY_PASCALDto>> => {
|
|
476
|
-
const queryParams = new URLSearchParams();
|
|
477
|
-
if (params.page) queryParams.set('page', params.page.toString());
|
|
478
|
-
if (params.pageSize) queryParams.set('pageSize', params.pageSize.toString());
|
|
479
|
-
if (params.search) queryParams.set('search', params.search);
|
|
480
|
-
if (params.sortColumn) queryParams.set('sortColumn', params.sortColumn);
|
|
481
|
-
if (params.sortDirection) queryParams.set('sortDirection', params.sortDirection);
|
|
482
|
-
|
|
483
|
-
return api.get(`/api/$module?${queryParams}`);
|
|
484
|
-
},
|
|
485
|
-
|
|
486
|
-
getById: async (id: string): Promise<$ENTITY_PASCALDto> => {
|
|
487
|
-
return api.get(`/api/$module/${id}`);
|
|
488
|
-
},
|
|
489
|
-
|
|
490
|
-
create: async (data: Create$ENTITY_PASCALRequest): Promise<$ENTITY_PASCALDto> => {
|
|
491
|
-
return api.post('/api/$module', data);
|
|
492
|
-
},
|
|
493
|
-
|
|
494
|
-
update: async (id: string, data: Update$ENTITY_PASCALRequest): Promise<$ENTITY_PASCALDto> => {
|
|
495
|
-
return api.put(`/api/$module/${id}`, data);
|
|
496
|
-
},
|
|
497
|
-
|
|
498
|
-
delete: async (id: string): Promise<void> => {
|
|
499
|
-
return api.delete(`/api/$module/${id}`);
|
|
500
|
-
},
|
|
501
|
-
};
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
---
|
|
505
|
-
|
|
506
|
-
## TEMPLATE: ROUTES (App.tsx)
|
|
507
|
-
|
|
508
|
-
### Detect App.tsx Routing Pattern FIRST
|
|
509
|
-
|
|
510
|
-
Before adding routes, **read App.tsx** and detect which pattern is used:
|
|
511
|
-
|
|
512
|
-
| Pattern | How to detect | Action |
|
|
513
|
-
|---------|---------------|--------|
|
|
514
|
-
| **Pattern A** (mergeRoutes) | `applicationRoutes: ApplicationRouteExtensions` present | Add to `applicationRoutes.{application}[]` array |
|
|
515
|
-
| **Pattern B** (JSX Routes) | `<Route path="/{application}" element={<{Layout} />}>` present | Insert `<Route>` children inside Layout wrapper |
|
|
516
|
-
|
|
517
|
-
### Pattern A: mergeRoutes (applicationRoutes array)
|
|
518
|
-
|
|
519
|
-
> **This is the DEFAULT pattern** generated by `smartstack init`.
|
|
520
|
-
> Routes added to `applicationRoutes` are automatically injected into BOTH standard and tenant-prefixed route trees by `mergeRoutes()`. No manual duplication needed.
|
|
521
|
-
|
|
522
|
-
```tsx
|
|
523
|
-
// Add to App.tsx — imports at top
|
|
524
|
-
import { $MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALPage';
|
|
525
|
-
import { $MODULE_PASCALDetailPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
|
|
526
|
-
import { Create$MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
|
|
527
|
-
|
|
528
|
-
// Add routes to applicationRoutes.{application}[] with RELATIVE paths (no leading /)
|
|
529
|
-
const applicationRoutes: ApplicationRouteExtensions = {
|
|
530
|
-
$APPLICATION: [
|
|
531
|
-
// ... existing routes ...
|
|
532
|
-
{ path: '$MODULE_KEBAB', element: <$MODULE_PASCALPage /> },
|
|
533
|
-
{ path: '$MODULE_KEBAB/new', element: <Create$MODULE_PASCALPage /> },
|
|
534
|
-
{ path: '$MODULE_KEBAB/:id', element: <$MODULE_PASCALDetailPage /> },
|
|
535
|
-
{ path: '$MODULE_KEBAB/:id/edit', element: <Create$MODULE_PASCALPage /> },
|
|
536
|
-
],
|
|
537
|
-
};
|
|
538
|
-
```
|
|
539
|
-
|
|
540
|
-
**mergeRoutes auto-generates redirects** for intermediate paths (e.g., `$APPLICATION` → `$APPLICATION/$DEFAULT_MODULE_KEBAB`) so you don't need to add index redirects manually.
|
|
541
|
-
|
|
542
|
-
**Application-to-Layout mapping (automatic via mergeRoutes):**
|
|
543
|
-
|
|
544
|
-
| Application key | Injected into Layout | Standard path | Tenant path |
|
|
545
|
-
|-----------------|---------------------|---------------|-------------|
|
|
546
|
-
| `administration` | `AppLayout` | `/administration/...` | `/t/:slug/administration/...` |
|
|
547
|
-
| `{application}` | `AppLayout` | `/{application}/...` | `/t/:slug/{application}/...` |
|
|
548
|
-
| `myspace` | `AppLayout` | `/myspace/...` | `/t/:slug/myspace/...` |
|
|
549
|
-
|
|
550
|
-
### Pattern B: JSX Routes (inside Layout wrapper)
|
|
551
|
-
|
|
552
|
-
> **Legacy pattern** — only used if App.tsx was manually restructured with JSX `<Route>` elements.
|
|
553
|
-
|
|
554
|
-
The unified `AppLayout` provides the application shell: **header with AvatarMenu**, sidebar, navigation. It renders child pages via React Router's `<Outlet />`.
|
|
555
|
-
|
|
556
|
-
**If routes are placed OUTSIDE the layout wrapper, the shell (header, sidebar, AvatarMenu) will NOT render. The page appears "naked" without any navigation.**
|
|
557
|
-
|
|
558
|
-
**Step-by-step insertion:**
|
|
559
|
-
|
|
560
|
-
1. Open `App.tsx`
|
|
561
|
-
2. Find the existing layout route for the target application:
|
|
562
|
-
- `administration` → `<Route path="/administration" element={<AppLayout />}>`
|
|
563
|
-
- `{application}` → `<Route path="/{application}" element={<AppLayout />}>`
|
|
564
|
-
- `myspace` → `<Route path="/myspace" element={<AppLayout />}>`
|
|
565
|
-
3. Add the new routes **INSIDE** that `<Route>` block
|
|
566
|
-
4. If a tenant-prefixed block exists (`/t/:slug/...`), add the routes there too
|
|
567
|
-
|
|
568
|
-
```tsx
|
|
569
|
-
// Add to App.tsx
|
|
570
|
-
|
|
571
|
-
import { $MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALPage';
|
|
572
|
-
import { $MODULE_PASCALDetailPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
|
|
573
|
-
import { Create$MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
|
|
574
|
-
|
|
575
|
-
// Find the EXISTING layout route and add routes INSIDE it:
|
|
576
|
-
<Route path="/$APPLICATION" element={<$APPLICATION_Layout />}>
|
|
577
|
-
{/* ... existing routes stay here ... */}
|
|
578
|
-
|
|
579
|
-
{/* NEW: $MODULE routes - added as children of the layout */}
|
|
580
|
-
<Route path="$MODULE_KEBAB">
|
|
581
|
-
<Route index element={<Navigate to="." replace />} />
|
|
582
|
-
<Route index element={<$MODULE_PASCALPage />} />
|
|
583
|
-
<Route path="new" element={<Create$MODULE_PASCALPage />} />
|
|
584
|
-
<Route path=":id" element={<$MODULE_PASCALDetailPage />} />
|
|
585
|
-
<Route path=":id/edit" element={<Create$MODULE_PASCALPage />} />
|
|
586
|
-
</Route>
|
|
587
|
-
</Route>
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
### ⚠️ CRITICAL RULES (both patterns)
|
|
591
|
-
|
|
592
|
-
**FORBIDDEN — Adding to clientRoutes[] with absolute paths:**
|
|
593
|
-
```tsx
|
|
594
|
-
// ❌ WRONG — bypasses layout entirely, shell will NOT render
|
|
595
|
-
const clientRoutes: RouteConfig[] = [
|
|
596
|
-
{ path: '/$APPLICATION_KEBAB/$MODULE_KEBAB', element: <$MODULE_PASCALPage /> },
|
|
597
|
-
];
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
`clientRoutes` is ONLY for routes **outside** SmartStack locked applications (e.g., `/about`, `/pricing`).
|
|
601
|
-
|
|
602
|
-
**FORBIDDEN — Flat routes outside layout:**
|
|
603
|
-
```tsx
|
|
604
|
-
// ❌ WRONG (Pattern B only) — flat route bypasses layout
|
|
605
|
-
<Route path="/$APPLICATION_KEBAB/$MODULE_KEBAB" element={<$MODULE_PASCALPage />} />
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
### Why nested/context routes?
|
|
609
|
-
|
|
610
|
-
| Aspect | clientRoutes (wrong) | applicationRoutes / nested (correct) |
|
|
611
|
-
|--------|---------------------|--------------------------------------|
|
|
612
|
-
| Shell rendered | No (bypasses layout) | Yes (Outlet pattern) |
|
|
613
|
-
| AvatarMenu visible | No | Yes |
|
|
614
|
-
| Tenant prefix | Manual duplication | Automatic (mergeRoutes) |
|
|
615
|
-
| Auto-redirects | None | Generated for intermediate paths |
|
|
616
|
-
| Permission check | Bypassed (no RouteGuard) | Enforced by RouteGuard |
|
|
617
|
-
|
|
618
|
-
---
|
|
619
|
-
|
|
620
|
-
## ⛔ FORBIDDEN PATTERNS
|
|
621
|
-
|
|
622
|
-
These patterns are **strictly prohibited** in generated frontend code:
|
|
623
|
-
|
|
624
|
-
| FORBIDDEN | REQUIRED |
|
|
625
|
-
|-----------|----------|
|
|
626
|
-
| `import axios from 'axios'` | `import { api } from '@/services/api/apiClient'` |
|
|
627
|
-
| `bg-blue-600`, `text-gray-900`, `bg-green-100` | `bg-[var(--color-accent-600)]`, `text-[var(--text-primary)]`, `bg-[var(--success-bg)]` |
|
|
628
|
-
| Any hardcoded Tailwind color (`bg-{color}-{shade}`) | CSS variables from style-guide.md |
|
|
629
|
-
| `rounded-lg`, `rounded-md` | `rounded-[var(--radius-card)]`, `rounded-[var(--radius-button)]` |
|
|
630
|
-
| Custom `<div>` cards for entity lists | `<EntityCard>` component (MANDATORY) |
|
|
631
|
-
| Flat routes outside Layout wrapper | Nested routes inside Layout wrapper |
|
|
632
|
-
| Route paths with PascalCase or concatenated words (e.g., `humanresources`, `timeManagement`) | Kebab-case route paths (e.g., `human-resources`, `time-management`) |
|
|
633
|
-
| `navigate(\`${basePath}/${item.id}\`)` in list row click | `navigate(item.id)` (relative navigation, no path duplication) |
|
|
634
|
-
| Only 2 languages (fr/en) | All 4 languages (fr/en/it/de) |
|
|
635
|
-
| `[Authorize]` without permissions | `[RequirePermission("navRoute.action")]` per endpoint |
|
|
636
|
-
| Missing error state | Error state with retry button (AlertCircle + underline) |
|
|
637
|
-
| Missing empty state | Empty state with icon + message |
|
|
638
|
-
| `new Date().toLocaleDateString()` without locale | Date formatting via i18n or Intl |
|
|
639
|
-
|
|
640
|
-
---
|
|
641
|
-
|
|
642
|
-
## FRONTEND CHECKLIST
|
|
643
|
-
|
|
644
|
-
### Structure
|
|
645
|
-
| Check | Status |
|
|
646
|
-
|-------|--------|
|
|
647
|
-
| ☐ Main page created (`$MODULE_PASCALPage.tsx`) | |
|
|
648
|
-
| ☐ ListView component created (`$MODULE_PASCALListView.tsx`) | |
|
|
649
|
-
| ☐ Preferences hook created (`use$MODULE_PASCALPreferences.ts`) | |
|
|
650
|
-
| ☐ API service created (uses `apiClient`, NOT raw axios) | |
|
|
651
|
-
| ☐ Routes added inside Layout wrapper in App.tsx | |
|
|
652
|
-
| ☐ Route path follows `/{application_kebab}/{module_kebab}` (kebab-case) | |
|
|
653
|
-
|
|
654
|
-
### Theme Compliance
|
|
655
|
-
| Check | Status |
|
|
656
|
-
|-------|--------|
|
|
657
|
-
| ☐ Zero hardcoded Tailwind colors (grep: `bg-blue-`, `text-gray-`, etc.) | |
|
|
658
|
-
| ☐ All colors use CSS variables (`var(--*)`) | |
|
|
659
|
-
| ☐ Border radius uses `var(--radius-*)` | |
|
|
660
|
-
| ☐ Buttons follow style-guide.md variants | |
|
|
661
|
-
|
|
662
|
-
### UI Components
|
|
663
|
-
| Check | Status |
|
|
664
|
-
|-------|--------|
|
|
665
|
-
| ☐ Grid view uses `EntityCard` (not custom divs) | |
|
|
666
|
-
| ☐ `ViewToggle` for list/grid switch | |
|
|
667
|
-
| ☐ Loading state (spinner) | |
|
|
668
|
-
| ☐ Error state (AlertCircle + retry button) | |
|
|
669
|
-
| ☐ Empty state (icon + message) | |
|
|
670
|
-
| ☐ Pagination component | |
|
|
671
|
-
|
|
672
|
-
### i18n
|
|
673
|
-
| Check | Status |
|
|
674
|
-
|-------|--------|
|
|
675
|
-
| ☐ French (fr) file created | |
|
|
676
|
-
| ☐ English (en) file created | |
|
|
677
|
-
| ☐ Italian (it) file created | |
|
|
678
|
-
| ☐ German (de) file created | |
|
|
679
|
-
| ☐ All 4 files have identical keys | |
|
|
680
|
-
|
|
681
|
-
### Build
|
|
682
|
-
| Check | Status |
|
|
683
|
-
|-------|--------|
|
|
684
|
-
| ☐ `npm run build` successful | |
|
|
685
|
-
| ☐ `npm run lint` successful | |
|
|
1
|
+
# Templates Frontend - Application Skill
|
|
2
|
+
|
|
3
|
+
> These templates generate React/TypeScript code for new applications/modules.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## FRONTEND ARCHITECTURE
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
web/smartstack-web/src/
|
|
11
|
+
├── pages/$APPLICATION/$MODULE/
|
|
12
|
+
│ ├── $MODULE_PASCALPage.tsx # Main page (list)
|
|
13
|
+
│ ├── $MODULE_PASCALDetailPage.tsx # Detail page
|
|
14
|
+
│ └── Create$MODULE_PASCALPage.tsx # Create page
|
|
15
|
+
├── components/$APPLICATION/$MODULE/
|
|
16
|
+
│ ├── $MODULE_PASCALListView.tsx # Reusable list component
|
|
17
|
+
│ ├── $MODULE_PASCALForm.tsx # CRUD form
|
|
18
|
+
│ └── $MODULE_PASCALFilters.tsx # Filters
|
|
19
|
+
├── components/common/
|
|
20
|
+
│ └── (shared components)
|
|
21
|
+
├── hooks/
|
|
22
|
+
│ ├── use$MODULE_PASCALPreferences.ts # Preferences hook
|
|
23
|
+
│ └── use$MODULE_PASCAL.ts # API hook
|
|
24
|
+
├── services/api/
|
|
25
|
+
│ └── $moduleApi.ts # API service
|
|
26
|
+
└── i18n/locales/
|
|
27
|
+
├── fr/$module.json
|
|
28
|
+
├── en/$module.json
|
|
29
|
+
├── it/$module.json
|
|
30
|
+
└── de/$module.json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ROUTE PATH VARIABLES
|
|
36
|
+
|
|
37
|
+
> **All route path segments MUST use kebab-case to match the navigation seed data.**
|
|
38
|
+
|
|
39
|
+
| Variable | Description | Example |
|
|
40
|
+
|----------|-------------|---------|
|
|
41
|
+
| `$APPLICATION_KEBAB` | kebab-case of application code | `human-resources` |
|
|
42
|
+
| `$MODULE_KEBAB` | kebab-case of module code | `time-management` |
|
|
43
|
+
|
|
44
|
+
**Transformation rule:** `PascalCase` → `kebab-case` via regex `([a-z])([A-Z])` → `$1-$2` + `toLowerCase()`
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
- `HumanResources` → `human-resources`
|
|
48
|
+
- `TimeManagement` → `time-management`
|
|
49
|
+
- `Employees` → `employees` (single word, no change needed)
|
|
50
|
+
|
|
51
|
+
> **FORBIDDEN:** Using raw `$APPLICATION` or `$MODULE` in route paths — they may produce non-kebab-case URLs (e.g., `humanresources` instead of `human-resources`). Always use `$APPLICATION_KEBAB` and `$MODULE_KEBAB` for route paths.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## TEMPLATE: MAIN PAGE
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// pages/$APPLICATION/$MODULE/$MODULE_PASCALPage.tsx
|
|
59
|
+
|
|
60
|
+
import { useTranslation } from 'react-i18next';
|
|
61
|
+
import { $MODULE_PASCALListView } from '@/components/$APPLICATION/$MODULE/$MODULE_PASCALListView';
|
|
62
|
+
|
|
63
|
+
export function $MODULE_PASCALPage() {
|
|
64
|
+
const { t } = useTranslation(['$module', 'common']);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<$MODULE_PASCALListView
|
|
68
|
+
title={t('$module:title')}
|
|
69
|
+
subtitle={t('$module:subtitle')}
|
|
70
|
+
createPath="new"
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## TEMPLATE: LIST VIEW (Reusable component)
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// components/$APPLICATION/$MODULE/$MODULE_PASCALListView.tsx
|
|
82
|
+
|
|
83
|
+
import { useState, useEffect } from 'react';
|
|
84
|
+
import { useNavigate } from 'react-router-dom';
|
|
85
|
+
import { useTranslation } from 'react-i18next';
|
|
86
|
+
import { Plus, Search, MoreVertical, Pencil, Trash2, Check, AlertCircle } from 'lucide-react';
|
|
87
|
+
import { api } from '@/services/api/apiClient';
|
|
88
|
+
import { use$MODULE_PASCALPreferences } from '@/hooks/use$MODULE_PASCALPreferences';
|
|
89
|
+
import { Pagination } from '@/components/common/Pagination';
|
|
90
|
+
import { ColumnSelector } from '@/components/common/ColumnSelector';
|
|
91
|
+
import { ViewToggle } from '@/components/ui/DataView';
|
|
92
|
+
import { EntityCard } from '@/components/ui/EntityCard'; // ⚠️ MANDATORY for Grid view
|
|
93
|
+
|
|
94
|
+
interface $ENTITY_PASCALDto {
|
|
95
|
+
id: string;
|
|
96
|
+
name: string;
|
|
97
|
+
description: string | null;
|
|
98
|
+
isActive: boolean;
|
|
99
|
+
createdAt: string;
|
|
100
|
+
updatedAt: string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface PaginatedResult {
|
|
104
|
+
items: $ENTITY_PASCALDto[];
|
|
105
|
+
totalCount: number;
|
|
106
|
+
page: number;
|
|
107
|
+
pageSize: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
interface $MODULE_PASCALListViewProps {
|
|
111
|
+
title: string;
|
|
112
|
+
subtitle?: string;
|
|
113
|
+
createPath?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const DEFAULT_COLUMNS = ['name', 'description', 'isActive', 'createdAt'];
|
|
117
|
+
|
|
118
|
+
export function $MODULE_PASCALListView({
|
|
119
|
+
title,
|
|
120
|
+
subtitle,
|
|
121
|
+
createPath,
|
|
122
|
+
}: $MODULE_PASCALListViewProps) {
|
|
123
|
+
const { t } = useTranslation(['$module', 'common']);
|
|
124
|
+
const navigate = useNavigate();
|
|
125
|
+
const {
|
|
126
|
+
pageSize,
|
|
127
|
+
sortColumn,
|
|
128
|
+
sortDirection,
|
|
129
|
+
visibleColumns,
|
|
130
|
+
viewMode,
|
|
131
|
+
setPageSize,
|
|
132
|
+
setSortColumn,
|
|
133
|
+
setSortDirection,
|
|
134
|
+
setVisibleColumns,
|
|
135
|
+
setViewMode,
|
|
136
|
+
} = use$MODULE_PASCALPreferences();
|
|
137
|
+
|
|
138
|
+
const [data, setData] = useState<PaginatedResult | null>(null);
|
|
139
|
+
const [loading, setLoading] = useState(true);
|
|
140
|
+
const [error, setError] = useState<string | null>(null);
|
|
141
|
+
const [page, setPage] = useState(1);
|
|
142
|
+
const [search, setSearchTerm] = useState('');
|
|
143
|
+
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
loadData();
|
|
146
|
+
}, [page, pageSize, sortColumn, sortDirection, search]);
|
|
147
|
+
|
|
148
|
+
const loadData = async () => {
|
|
149
|
+
try {
|
|
150
|
+
setLoading(true);
|
|
151
|
+
setError(null);
|
|
152
|
+
const params = new URLSearchParams({
|
|
153
|
+
page: page.toString(),
|
|
154
|
+
pageSize: pageSize.toString(),
|
|
155
|
+
sortColumn,
|
|
156
|
+
sortDirection,
|
|
157
|
+
...(search && { search }),
|
|
158
|
+
});
|
|
159
|
+
const result = await api.get<PaginatedResult>(`/api/$module?${params}`);
|
|
160
|
+
setData(result);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
setError(t('common:errors.loadFailed'));
|
|
163
|
+
console.error('Failed to load $module:', err);
|
|
164
|
+
} finally {
|
|
165
|
+
setLoading(false);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const handleSort = (column: string) => {
|
|
170
|
+
if (sortColumn === column) {
|
|
171
|
+
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
|
172
|
+
} else {
|
|
173
|
+
setSortColumn(column);
|
|
174
|
+
setSortDirection('asc');
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const handleDelete = async (id: string) => {
|
|
179
|
+
if (!confirm(t('common:confirmDelete'))) return;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await api.delete(`/api/$module/${id}`);
|
|
183
|
+
loadData();
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.error('Failed to delete:', err);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const columns = [
|
|
190
|
+
{ key: 'name', label: t('$module:columns.name'), sortable: true },
|
|
191
|
+
{ key: 'description', label: t('$module:columns.description'), sortable: false },
|
|
192
|
+
{ key: 'isActive', label: t('$module:columns.status'), sortable: true },
|
|
193
|
+
{ key: 'createdAt', label: t('$module:columns.createdAt'), sortable: true },
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div className="space-y-6">
|
|
198
|
+
{/* Header */}
|
|
199
|
+
<div className="flex items-center justify-between">
|
|
200
|
+
<div>
|
|
201
|
+
<h1 className="text-2xl font-bold text-[var(--text-primary)]">{title}</h1>
|
|
202
|
+
{subtitle && (
|
|
203
|
+
<p className="text-[var(--text-secondary)] mt-1">{subtitle}</p>
|
|
204
|
+
)}
|
|
205
|
+
</div>
|
|
206
|
+
{createPath && (
|
|
207
|
+
<button
|
|
208
|
+
onClick={() => navigate(createPath)}
|
|
209
|
+
className="flex items-center gap-2 px-4 py-2 bg-[var(--color-accent-500)] hover:bg-[var(--color-accent-600)] text-white font-medium transition-colors"
|
|
210
|
+
style={{ borderRadius: 'var(--radius-button)' }}
|
|
211
|
+
>
|
|
212
|
+
<Plus className="w-4 h-4" />
|
|
213
|
+
{t('common:actions.create')}
|
|
214
|
+
</button>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
{/* Toolbar */}
|
|
219
|
+
<div className="flex items-center justify-between gap-4">
|
|
220
|
+
<div className="relative flex-1 max-w-md">
|
|
221
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-[var(--text-muted)]" />
|
|
222
|
+
<input
|
|
223
|
+
type="text"
|
|
224
|
+
placeholder={t('common:search.placeholder')}
|
|
225
|
+
value={search}
|
|
226
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
227
|
+
className="w-full pl-10 pr-4 py-2 bg-[var(--bg-primary)] border border-[var(--border-color)] text-sm focus:outline-none focus:border-[var(--color-accent-500)]"
|
|
228
|
+
style={{ borderRadius: 'var(--radius-input)' }}
|
|
229
|
+
/>
|
|
230
|
+
</div>
|
|
231
|
+
<div className="flex items-center gap-2">
|
|
232
|
+
<ColumnSelector
|
|
233
|
+
columns={columns}
|
|
234
|
+
visibleColumns={visibleColumns}
|
|
235
|
+
onChange={setVisibleColumns}
|
|
236
|
+
/>
|
|
237
|
+
<ViewToggle value={viewMode} onChange={setViewMode} />
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Content */}
|
|
242
|
+
{loading ? (
|
|
243
|
+
<div className="flex items-center justify-center py-12">
|
|
244
|
+
<div className="animate-spin h-8 w-8 border-4 border-[var(--color-accent-500)] border-t-transparent rounded-full" />
|
|
245
|
+
</div>
|
|
246
|
+
) : error ? (
|
|
247
|
+
<div className="p-4 bg-[var(--error-bg)] border border-[var(--error-border)] rounded-[var(--radius-card)] flex items-center gap-3">
|
|
248
|
+
<AlertCircle className="w-5 h-5 text-[var(--error-dot)] flex-shrink-0" />
|
|
249
|
+
<span className="text-[var(--error-text)]">{error}</span>
|
|
250
|
+
<button onClick={() => fetchData()} className="ml-auto text-sm text-[var(--error-text)] hover:underline">
|
|
251
|
+
{t('common:actions.retry')}
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
) : data?.items.length === 0 ? (
|
|
255
|
+
<div className="text-center py-16">
|
|
256
|
+
<div className="w-16 h-16 mx-auto rounded-full bg-[var(--bg-secondary)] flex items-center justify-center mb-4">
|
|
257
|
+
<Search className="w-8 h-8 text-[var(--text-tertiary)]" />
|
|
258
|
+
</div>
|
|
259
|
+
<p className="text-[var(--text-secondary)]">{t('$module:list.empty')}</p>
|
|
260
|
+
</div>
|
|
261
|
+
) : viewMode === 'list' ? (
|
|
262
|
+
<div className="bg-[var(--bg-card)] border border-[var(--item-color-border)] overflow-hidden" style={{ borderRadius: 'var(--radius-card)' }}>
|
|
263
|
+
<table className="w-full">
|
|
264
|
+
<thead>
|
|
265
|
+
<tr className="border-b border-[var(--item-color-border)]">
|
|
266
|
+
{columns.filter(col => visibleColumns.includes(col.key)).map((col) => (
|
|
267
|
+
<th
|
|
268
|
+
key={col.key}
|
|
269
|
+
className={`px-4 py-3 text-left text-sm font-medium text-[var(--text-secondary)] ${
|
|
270
|
+
col.sortable ? 'cursor-pointer hover:text-[var(--text-primary)]' : ''
|
|
271
|
+
}`}
|
|
272
|
+
onClick={() => col.sortable && handleSort(col.key)}
|
|
273
|
+
>
|
|
274
|
+
{col.label}
|
|
275
|
+
{sortColumn === col.key && (
|
|
276
|
+
<span className="ml-1">{sortDirection === 'asc' ? '↑' : '↓'}</span>
|
|
277
|
+
)}
|
|
278
|
+
</th>
|
|
279
|
+
))}
|
|
280
|
+
<th className="px-4 py-3 text-right text-sm font-medium text-[var(--text-secondary)]">
|
|
281
|
+
{t('common:actions.title')}
|
|
282
|
+
</th>
|
|
283
|
+
</tr>
|
|
284
|
+
</thead>
|
|
285
|
+
<tbody>
|
|
286
|
+
{data?.items.map((item) => (
|
|
287
|
+
<tr
|
|
288
|
+
key={item.id}
|
|
289
|
+
className="border-b border-[var(--item-color-border)] hover:bg-[var(--bg-hover)] cursor-pointer"
|
|
290
|
+
onClick={() => navigate(item.id)}
|
|
291
|
+
>
|
|
292
|
+
{visibleColumns.includes('name') && (
|
|
293
|
+
<td className="px-4 py-3 text-sm text-[var(--text-primary)] font-medium">
|
|
294
|
+
{item.name}
|
|
295
|
+
</td>
|
|
296
|
+
)}
|
|
297
|
+
{visibleColumns.includes('description') && (
|
|
298
|
+
<td className="px-4 py-3 text-sm text-[var(--text-secondary)]">
|
|
299
|
+
{item.description || '-'}
|
|
300
|
+
</td>
|
|
301
|
+
)}
|
|
302
|
+
{visibleColumns.includes('isActive') && (
|
|
303
|
+
<td className="px-4 py-3">
|
|
304
|
+
<span
|
|
305
|
+
className={`inline-flex items-center px-2 py-1 text-xs font-medium ${
|
|
306
|
+
item.isActive
|
|
307
|
+
? 'bg-[var(--success-bg)] text-[var(--success-text)]'
|
|
308
|
+
: 'bg-[var(--bg-tertiary)] text-[var(--text-secondary)]'
|
|
309
|
+
}`}
|
|
310
|
+
style={{ borderRadius: 'var(--radius-badge)' }}
|
|
311
|
+
>
|
|
312
|
+
{item.isActive ? t('common:status.active') : t('common:status.inactive')}
|
|
313
|
+
</span>
|
|
314
|
+
</td>
|
|
315
|
+
)}
|
|
316
|
+
{visibleColumns.includes('createdAt') && (
|
|
317
|
+
<td className="px-4 py-3 text-sm text-[var(--text-secondary)]">
|
|
318
|
+
{new Date(item.createdAt).toLocaleDateString()}
|
|
319
|
+
</td>
|
|
320
|
+
)}
|
|
321
|
+
<td className="px-4 py-3 text-right">
|
|
322
|
+
<div className="flex items-center justify-end gap-2">
|
|
323
|
+
<button
|
|
324
|
+
onClick={(e) => {
|
|
325
|
+
e.stopPropagation();
|
|
326
|
+
navigate(`${item.id}/edit`);
|
|
327
|
+
}}
|
|
328
|
+
className="p-1 hover:bg-[var(--bg-tertiary)]"
|
|
329
|
+
style={{ borderRadius: 'var(--radius-button)' }}
|
|
330
|
+
>
|
|
331
|
+
<Pencil className="w-4 h-4 text-[var(--text-secondary)]" />
|
|
332
|
+
</button>
|
|
333
|
+
<button
|
|
334
|
+
onClick={(e) => {
|
|
335
|
+
e.stopPropagation();
|
|
336
|
+
handleDelete(item.id);
|
|
337
|
+
}}
|
|
338
|
+
className="p-1 hover:bg-[var(--error-bg)]"
|
|
339
|
+
style={{ borderRadius: 'var(--radius-button)' }}
|
|
340
|
+
>
|
|
341
|
+
<Trash2 className="w-4 h-4 text-[var(--error-text)]" />
|
|
342
|
+
</button>
|
|
343
|
+
</div>
|
|
344
|
+
</td>
|
|
345
|
+
</tr>
|
|
346
|
+
))}
|
|
347
|
+
</tbody>
|
|
348
|
+
</table>
|
|
349
|
+
</div>
|
|
350
|
+
) : (
|
|
351
|
+
/* Grid view - ⚠️ MANDATORY: use EntityCard */
|
|
352
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
|
353
|
+
{data?.items.map((item) => (
|
|
354
|
+
<EntityCard
|
|
355
|
+
key={item.id}
|
|
356
|
+
avatar={{ letter: item.name[0].toUpperCase(), color: 'var(--color-accent-500)' }}
|
|
357
|
+
title={item.name}
|
|
358
|
+
subtitle={item.code}
|
|
359
|
+
description={item.description}
|
|
360
|
+
badge={item.isActive ? {
|
|
361
|
+
icon: Check,
|
|
362
|
+
tooltip: t('common:status.active'),
|
|
363
|
+
color: 'var(--success-text)'
|
|
364
|
+
} : undefined}
|
|
365
|
+
stats={`${t('common:createdAt')}: ${new Date(item.createdAt).toLocaleDateString()}`}
|
|
366
|
+
onClick={() => navigate(item.id)}
|
|
367
|
+
actions={[
|
|
368
|
+
{ label: t('common:actions.view'), onClick: () => navigate(item.id), variant: 'primary' },
|
|
369
|
+
]}
|
|
370
|
+
/>
|
|
371
|
+
))}
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
|
|
375
|
+
{/* Pagination */}
|
|
376
|
+
{data && (
|
|
377
|
+
<Pagination
|
|
378
|
+
page={data.page}
|
|
379
|
+
pageSize={data.pageSize}
|
|
380
|
+
totalCount={data.totalCount}
|
|
381
|
+
onPageChange={setPage}
|
|
382
|
+
onPageSizeChange={setPageSize}
|
|
383
|
+
/>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## TEMPLATE: PREFERENCES HOOK
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
// hooks/use$MODULE_PASCALPreferences.ts
|
|
396
|
+
|
|
397
|
+
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
|
|
398
|
+
|
|
399
|
+
const DEFAULT_COLUMNS = ['name', 'description', 'isActive', 'createdAt'];
|
|
400
|
+
|
|
401
|
+
export function use$MODULE_PASCALPreferences() {
|
|
402
|
+
const { preferences, updatePreference } = useUserPreferences();
|
|
403
|
+
const modulePrefs = preferences.$module || {};
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
// Getters with defaults
|
|
407
|
+
pageSize: modulePrefs.pageSize ?? 10,
|
|
408
|
+
sortColumn: modulePrefs.sortColumn ?? 'createdAt',
|
|
409
|
+
sortDirection: modulePrefs.sortDirection ?? 'desc',
|
|
410
|
+
filters: modulePrefs.filters ?? {},
|
|
411
|
+
visibleColumns: modulePrefs.visibleColumns ?? DEFAULT_COLUMNS,
|
|
412
|
+
viewMode: modulePrefs.viewMode ?? 'list',
|
|
413
|
+
|
|
414
|
+
// Setters
|
|
415
|
+
setPageSize: (size: number) =>
|
|
416
|
+
updatePreference('$module.pageSize', size),
|
|
417
|
+
setSortColumn: (col: string) =>
|
|
418
|
+
updatePreference('$module.sortColumn', col),
|
|
419
|
+
setSortDirection: (dir: 'asc' | 'desc') =>
|
|
420
|
+
updatePreference('$module.sortDirection', dir),
|
|
421
|
+
setFilters: (filters: Record<string, unknown>) =>
|
|
422
|
+
updatePreference('$module.filters', filters),
|
|
423
|
+
setVisibleColumns: (cols: string[]) =>
|
|
424
|
+
updatePreference('$module.visibleColumns', cols),
|
|
425
|
+
setViewMode: (mode: 'list' | 'grid') =>
|
|
426
|
+
updatePreference('$module.viewMode', mode),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## TEMPLATE: SERVICE API
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
// services/api/$moduleApi.ts
|
|
437
|
+
|
|
438
|
+
import { api } from './apiClient';
|
|
439
|
+
|
|
440
|
+
export interface $ENTITY_PASCALDto {
|
|
441
|
+
id: string;
|
|
442
|
+
name: string;
|
|
443
|
+
description: string | null;
|
|
444
|
+
isActive: boolean;
|
|
445
|
+
createdAt: string;
|
|
446
|
+
updatedAt: string | null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export interface Create$ENTITY_PASCALRequest {
|
|
450
|
+
name: string;
|
|
451
|
+
description?: string;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export interface Update$ENTITY_PASCALRequest {
|
|
455
|
+
name: string;
|
|
456
|
+
description?: string;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export interface PaginatedResult<T> {
|
|
460
|
+
items: T[];
|
|
461
|
+
totalCount: number;
|
|
462
|
+
page: number;
|
|
463
|
+
pageSize: number;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export interface PaginationParams {
|
|
467
|
+
page?: number;
|
|
468
|
+
pageSize?: number;
|
|
469
|
+
search?: string;
|
|
470
|
+
sortColumn?: string;
|
|
471
|
+
sortDirection?: 'asc' | 'desc';
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export const $moduleApi = {
|
|
475
|
+
getAll: async (params: PaginationParams = {}): Promise<PaginatedResult<$ENTITY_PASCALDto>> => {
|
|
476
|
+
const queryParams = new URLSearchParams();
|
|
477
|
+
if (params.page) queryParams.set('page', params.page.toString());
|
|
478
|
+
if (params.pageSize) queryParams.set('pageSize', params.pageSize.toString());
|
|
479
|
+
if (params.search) queryParams.set('search', params.search);
|
|
480
|
+
if (params.sortColumn) queryParams.set('sortColumn', params.sortColumn);
|
|
481
|
+
if (params.sortDirection) queryParams.set('sortDirection', params.sortDirection);
|
|
482
|
+
|
|
483
|
+
return api.get(`/api/$module?${queryParams}`);
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
getById: async (id: string): Promise<$ENTITY_PASCALDto> => {
|
|
487
|
+
return api.get(`/api/$module/${id}`);
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
create: async (data: Create$ENTITY_PASCALRequest): Promise<$ENTITY_PASCALDto> => {
|
|
491
|
+
return api.post('/api/$module', data);
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
update: async (id: string, data: Update$ENTITY_PASCALRequest): Promise<$ENTITY_PASCALDto> => {
|
|
495
|
+
return api.put(`/api/$module/${id}`, data);
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
delete: async (id: string): Promise<void> => {
|
|
499
|
+
return api.delete(`/api/$module/${id}`);
|
|
500
|
+
},
|
|
501
|
+
};
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## TEMPLATE: ROUTES (App.tsx)
|
|
507
|
+
|
|
508
|
+
### Detect App.tsx Routing Pattern FIRST
|
|
509
|
+
|
|
510
|
+
Before adding routes, **read App.tsx** and detect which pattern is used:
|
|
511
|
+
|
|
512
|
+
| Pattern | How to detect | Action |
|
|
513
|
+
|---------|---------------|--------|
|
|
514
|
+
| **Pattern A** (mergeRoutes) | `applicationRoutes: ApplicationRouteExtensions` present | Add to `applicationRoutes.{application}[]` array |
|
|
515
|
+
| **Pattern B** (JSX Routes) | `<Route path="/{application}" element={<{Layout} />}>` present | Insert `<Route>` children inside Layout wrapper |
|
|
516
|
+
|
|
517
|
+
### Pattern A: mergeRoutes (applicationRoutes array)
|
|
518
|
+
|
|
519
|
+
> **This is the DEFAULT pattern** generated by `smartstack init`.
|
|
520
|
+
> Routes added to `applicationRoutes` are automatically injected into BOTH standard and tenant-prefixed route trees by `mergeRoutes()`. No manual duplication needed.
|
|
521
|
+
|
|
522
|
+
```tsx
|
|
523
|
+
// Add to App.tsx — imports at top
|
|
524
|
+
import { $MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALPage';
|
|
525
|
+
import { $MODULE_PASCALDetailPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
|
|
526
|
+
import { Create$MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
|
|
527
|
+
|
|
528
|
+
// Add routes to applicationRoutes.{application}[] with RELATIVE paths (no leading /)
|
|
529
|
+
const applicationRoutes: ApplicationRouteExtensions = {
|
|
530
|
+
$APPLICATION: [
|
|
531
|
+
// ... existing routes ...
|
|
532
|
+
{ path: '$MODULE_KEBAB', element: <$MODULE_PASCALPage /> },
|
|
533
|
+
{ path: '$MODULE_KEBAB/new', element: <Create$MODULE_PASCALPage /> },
|
|
534
|
+
{ path: '$MODULE_KEBAB/:id', element: <$MODULE_PASCALDetailPage /> },
|
|
535
|
+
{ path: '$MODULE_KEBAB/:id/edit', element: <Create$MODULE_PASCALPage /> },
|
|
536
|
+
],
|
|
537
|
+
};
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**mergeRoutes auto-generates redirects** for intermediate paths (e.g., `$APPLICATION` → `$APPLICATION/$DEFAULT_MODULE_KEBAB`) so you don't need to add index redirects manually.
|
|
541
|
+
|
|
542
|
+
**Application-to-Layout mapping (automatic via mergeRoutes):**
|
|
543
|
+
|
|
544
|
+
| Application key | Injected into Layout | Standard path | Tenant path |
|
|
545
|
+
|-----------------|---------------------|---------------|-------------|
|
|
546
|
+
| `administration` | `AppLayout` | `/administration/...` | `/t/:slug/administration/...` |
|
|
547
|
+
| `{application}` | `AppLayout` | `/{application}/...` | `/t/:slug/{application}/...` |
|
|
548
|
+
| `myspace` | `AppLayout` | `/myspace/...` | `/t/:slug/myspace/...` |
|
|
549
|
+
|
|
550
|
+
### Pattern B: JSX Routes (inside Layout wrapper)
|
|
551
|
+
|
|
552
|
+
> **Legacy pattern** — only used if App.tsx was manually restructured with JSX `<Route>` elements.
|
|
553
|
+
|
|
554
|
+
The unified `AppLayout` provides the application shell: **header with AvatarMenu**, sidebar, navigation. It renders child pages via React Router's `<Outlet />`.
|
|
555
|
+
|
|
556
|
+
**If routes are placed OUTSIDE the layout wrapper, the shell (header, sidebar, AvatarMenu) will NOT render. The page appears "naked" without any navigation.**
|
|
557
|
+
|
|
558
|
+
**Step-by-step insertion:**
|
|
559
|
+
|
|
560
|
+
1. Open `App.tsx`
|
|
561
|
+
2. Find the existing layout route for the target application:
|
|
562
|
+
- `administration` → `<Route path="/administration" element={<AppLayout />}>`
|
|
563
|
+
- `{application}` → `<Route path="/{application}" element={<AppLayout />}>`
|
|
564
|
+
- `myspace` → `<Route path="/myspace" element={<AppLayout />}>`
|
|
565
|
+
3. Add the new routes **INSIDE** that `<Route>` block
|
|
566
|
+
4. If a tenant-prefixed block exists (`/t/:slug/...`), add the routes there too
|
|
567
|
+
|
|
568
|
+
```tsx
|
|
569
|
+
// Add to App.tsx
|
|
570
|
+
|
|
571
|
+
import { $MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALPage';
|
|
572
|
+
import { $MODULE_PASCALDetailPage } from '@/pages/$APPLICATION/$MODULE/$MODULE_PASCALDetailPage';
|
|
573
|
+
import { Create$MODULE_PASCALPage } from '@/pages/$APPLICATION/$MODULE/Create$MODULE_PASCALPage';
|
|
574
|
+
|
|
575
|
+
// Find the EXISTING layout route and add routes INSIDE it:
|
|
576
|
+
<Route path="/$APPLICATION" element={<$APPLICATION_Layout />}>
|
|
577
|
+
{/* ... existing routes stay here ... */}
|
|
578
|
+
|
|
579
|
+
{/* NEW: $MODULE routes - added as children of the layout */}
|
|
580
|
+
<Route path="$MODULE_KEBAB">
|
|
581
|
+
<Route index element={<Navigate to="." replace />} />
|
|
582
|
+
<Route index element={<$MODULE_PASCALPage />} />
|
|
583
|
+
<Route path="new" element={<Create$MODULE_PASCALPage />} />
|
|
584
|
+
<Route path=":id" element={<$MODULE_PASCALDetailPage />} />
|
|
585
|
+
<Route path=":id/edit" element={<Create$MODULE_PASCALPage />} />
|
|
586
|
+
</Route>
|
|
587
|
+
</Route>
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
### ⚠️ CRITICAL RULES (both patterns)
|
|
591
|
+
|
|
592
|
+
**FORBIDDEN — Adding to clientRoutes[] with absolute paths:**
|
|
593
|
+
```tsx
|
|
594
|
+
// ❌ WRONG — bypasses layout entirely, shell will NOT render
|
|
595
|
+
const clientRoutes: RouteConfig[] = [
|
|
596
|
+
{ path: '/$APPLICATION_KEBAB/$MODULE_KEBAB', element: <$MODULE_PASCALPage /> },
|
|
597
|
+
];
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
`clientRoutes` is ONLY for routes **outside** SmartStack locked applications (e.g., `/about`, `/pricing`).
|
|
601
|
+
|
|
602
|
+
**FORBIDDEN — Flat routes outside layout:**
|
|
603
|
+
```tsx
|
|
604
|
+
// ❌ WRONG (Pattern B only) — flat route bypasses layout
|
|
605
|
+
<Route path="/$APPLICATION_KEBAB/$MODULE_KEBAB" element={<$MODULE_PASCALPage />} />
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Why nested/context routes?
|
|
609
|
+
|
|
610
|
+
| Aspect | clientRoutes (wrong) | applicationRoutes / nested (correct) |
|
|
611
|
+
|--------|---------------------|--------------------------------------|
|
|
612
|
+
| Shell rendered | No (bypasses layout) | Yes (Outlet pattern) |
|
|
613
|
+
| AvatarMenu visible | No | Yes |
|
|
614
|
+
| Tenant prefix | Manual duplication | Automatic (mergeRoutes) |
|
|
615
|
+
| Auto-redirects | None | Generated for intermediate paths |
|
|
616
|
+
| Permission check | Bypassed (no RouteGuard) | Enforced by RouteGuard |
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
## ⛔ FORBIDDEN PATTERNS
|
|
621
|
+
|
|
622
|
+
These patterns are **strictly prohibited** in generated frontend code:
|
|
623
|
+
|
|
624
|
+
| FORBIDDEN | REQUIRED |
|
|
625
|
+
|-----------|----------|
|
|
626
|
+
| `import axios from 'axios'` | `import { api } from '@/services/api/apiClient'` |
|
|
627
|
+
| `bg-blue-600`, `text-gray-900`, `bg-green-100` | `bg-[var(--color-accent-600)]`, `text-[var(--text-primary)]`, `bg-[var(--success-bg)]` |
|
|
628
|
+
| Any hardcoded Tailwind color (`bg-{color}-{shade}`) | CSS variables from style-guide.md |
|
|
629
|
+
| `rounded-lg`, `rounded-md` | `rounded-[var(--radius-card)]`, `rounded-[var(--radius-button)]` |
|
|
630
|
+
| Custom `<div>` cards for entity lists | `<EntityCard>` component (MANDATORY) |
|
|
631
|
+
| Flat routes outside Layout wrapper | Nested routes inside Layout wrapper |
|
|
632
|
+
| Route paths with PascalCase or concatenated words (e.g., `humanresources`, `timeManagement`) | Kebab-case route paths (e.g., `human-resources`, `time-management`) |
|
|
633
|
+
| `navigate(\`${basePath}/${item.id}\`)` in list row click | `navigate(item.id)` (relative navigation, no path duplication) |
|
|
634
|
+
| Only 2 languages (fr/en) | All 4 languages (fr/en/it/de) |
|
|
635
|
+
| `[Authorize]` without permissions | `[RequirePermission("navRoute.action")]` per endpoint |
|
|
636
|
+
| Missing error state | Error state with retry button (AlertCircle + underline) |
|
|
637
|
+
| Missing empty state | Empty state with icon + message |
|
|
638
|
+
| `new Date().toLocaleDateString()` without locale | Date formatting via i18n or Intl |
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## FRONTEND CHECKLIST
|
|
643
|
+
|
|
644
|
+
### Structure
|
|
645
|
+
| Check | Status |
|
|
646
|
+
|-------|--------|
|
|
647
|
+
| ☐ Main page created (`$MODULE_PASCALPage.tsx`) | |
|
|
648
|
+
| ☐ ListView component created (`$MODULE_PASCALListView.tsx`) | |
|
|
649
|
+
| ☐ Preferences hook created (`use$MODULE_PASCALPreferences.ts`) | |
|
|
650
|
+
| ☐ API service created (uses `apiClient`, NOT raw axios) | |
|
|
651
|
+
| ☐ Routes added inside Layout wrapper in App.tsx | |
|
|
652
|
+
| ☐ Route path follows `/{application_kebab}/{module_kebab}` (kebab-case) | |
|
|
653
|
+
|
|
654
|
+
### Theme Compliance
|
|
655
|
+
| Check | Status |
|
|
656
|
+
|-------|--------|
|
|
657
|
+
| ☐ Zero hardcoded Tailwind colors (grep: `bg-blue-`, `text-gray-`, etc.) | |
|
|
658
|
+
| ☐ All colors use CSS variables (`var(--*)`) | |
|
|
659
|
+
| ☐ Border radius uses `var(--radius-*)` | |
|
|
660
|
+
| ☐ Buttons follow style-guide.md variants | |
|
|
661
|
+
|
|
662
|
+
### UI Components
|
|
663
|
+
| Check | Status |
|
|
664
|
+
|-------|--------|
|
|
665
|
+
| ☐ Grid view uses `EntityCard` (not custom divs) | |
|
|
666
|
+
| ☐ `ViewToggle` for list/grid switch | |
|
|
667
|
+
| ☐ Loading state (spinner) | |
|
|
668
|
+
| ☐ Error state (AlertCircle + retry button) | |
|
|
669
|
+
| ☐ Empty state (icon + message) | |
|
|
670
|
+
| ☐ Pagination component | |
|
|
671
|
+
|
|
672
|
+
### i18n
|
|
673
|
+
| Check | Status |
|
|
674
|
+
|-------|--------|
|
|
675
|
+
| ☐ French (fr) file created | |
|
|
676
|
+
| ☐ English (en) file created | |
|
|
677
|
+
| ☐ Italian (it) file created | |
|
|
678
|
+
| ☐ German (de) file created | |
|
|
679
|
+
| ☐ All 4 files have identical keys | |
|
|
680
|
+
|
|
681
|
+
### Build
|
|
682
|
+
| Check | Status |
|
|
683
|
+
|-------|--------|
|
|
684
|
+
| ☐ `npm run build` successful | |
|
|
685
|
+
| ☐ `npm run lint` successful | |
|