evnict-kit 0.2.1
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/README.md +19 -0
- package/bin/cli.js +38 -0
- package/package.json +48 -0
- package/src/commands/add.js +129 -0
- package/src/commands/init-check.js +19 -0
- package/src/commands/init-context.js +37 -0
- package/src/commands/init-rules.js +42 -0
- package/src/commands/init-workflow.js +36 -0
- package/src/commands/init.js +722 -0
- package/src/utils/config.js +167 -0
- package/src/utils/file.js +53 -0
- package/templates/GETTING-STARTED.md +196 -0
- package/templates/content/context/AGENTS.md.template +462 -0
- package/templates/content/rules/01-evnict-kit-general-rules.md +303 -0
- package/templates/content/rules/02-evnict-kit-security-rules.md +423 -0
- package/templates/content/rules/03-evnict-kit-backend-conventions.md +383 -0
- package/templates/content/rules/04-evnict-kit-frontend-conventions.md +402 -0
- package/templates/content/rules/05-evnict-kit-project-conventions.md +228 -0
- package/templates/content/skills/evnict-kit-brainstorm/SKILL.md +140 -0
- package/templates/content/skills/evnict-kit-bug-fix/SKILL.md +108 -0
- package/templates/content/skills/evnict-kit-checkpoint/SKILL.md +156 -0
- package/templates/content/skills/evnict-kit-code-review/SKILL.md +158 -0
- package/templates/content/skills/evnict-kit-coordinate/SKILL.md +274 -0
- package/templates/content/skills/evnict-kit-create-api/SKILL.md +281 -0
- package/templates/content/skills/evnict-kit-create-component/SKILL.md +263 -0
- package/templates/content/skills/evnict-kit-create-page/SKILL.md +247 -0
- package/templates/content/skills/evnict-kit-database-migration/SKILL.md +164 -0
- package/templates/content/skills/evnict-kit-doc-postmortem/SKILL.md +93 -0
- package/templates/content/skills/evnict-kit-finish-branch/SKILL.md +87 -0
- package/templates/content/skills/evnict-kit-fix-attt/SKILL.md +129 -0
- package/templates/content/skills/evnict-kit-fix-business-logic/SKILL.md +89 -0
- package/templates/content/skills/evnict-kit-git-worktrees/SKILL.md +104 -0
- package/templates/content/skills/evnict-kit-merge-checklist/SKILL.md +108 -0
- package/templates/content/skills/evnict-kit-onboard/SKILL.md +143 -0
- package/templates/content/skills/evnict-kit-prompt-standard/SKILL.md +103 -0
- package/templates/content/skills/evnict-kit-receiving-review/SKILL.md +89 -0
- package/templates/content/skills/evnict-kit-security-audit/SKILL.md +190 -0
- package/templates/content/skills/evnict-kit-spec/SKILL.md +237 -0
- package/templates/content/skills/evnict-kit-tdd/SKILL.md +413 -0
- package/templates/content/skills/evnict-kit-wiki/SKILL.md +412 -0
- package/templates/content/workflows/evnict-kit-archive-wiki.md +100 -0
- package/templates/content/workflows/evnict-kit-attt.md +100 -0
- package/templates/content/workflows/evnict-kit-bug-fix.md +107 -0
- package/templates/content/workflows/evnict-kit-feature-large.md +393 -0
- package/templates/content/workflows/evnict-kit-feature-small.md +86 -0
- package/templates/content/workflows/evnict-kit-handoff.md +243 -0
- package/templates/content/workflows/evnict-kit-implement.md +247 -0
- package/templates/content/workflows/evnict-kit-init-check.md +76 -0
- package/templates/content/workflows/evnict-kit-init-context.md +58 -0
- package/templates/content/workflows/evnict-kit-init-rules.md +114 -0
- package/templates/content/workflows/evnict-kit-init-wiki.md +80 -0
- package/templates/content/workflows/evnict-kit-plan.md +308 -0
- package/templates/content/workflows/evnict-kit-review.md +53 -0
- package/templates/content/workflows/evnict-kit-spec-archive.md +53 -0
- package/templates/content/workflows/evnict-kit-wiki-archive-feature.md +164 -0
- package/templates/content/workflows/evnict-kit-wiki-query.md +91 -0
- package/templates/content/workflows/evnict-kit-wiki-scan-project.md +272 -0
- package/templates/context/AGENT.md.template +9 -0
- package/templates/context/AGENTS.md.template +462 -0
- package/templates/context/CLAUDE.md.template +301 -0
- package/templates/context/copilot-instructions.md.template +60 -0
- package/templates/context/cursorrules.template +114 -0
- package/templates/instruct/Instruct-Agent-AI.be.md +96 -0
- package/templates/instruct/Instruct-Agent-AI.fe.md +79 -0
- package/templates/rules/antigravity/01-evnict-kit-general-rules.md +303 -0
- package/templates/rules/antigravity/02-evnict-kit-security-rules.md +423 -0
- package/templates/rules/antigravity/03-evnict-kit-backend-conventions.md +383 -0
- package/templates/rules/antigravity/04-evnict-kit-frontend-conventions.md +402 -0
- package/templates/rules/antigravity/05-evnict-kit-project-conventions.md +228 -0
- package/templates/rules/claude/README.md +8 -0
- package/templates/rules/cursor/01-evnict-kit-general-rules.mdc +46 -0
- package/templates/rules/cursor/02-evnict-kit-security-rules.mdc +46 -0
- package/templates/rules/cursor/03-evnict-kit-backend-conventions.mdc +50 -0
- package/templates/rules/cursor/04-evnict-kit-frontend-conventions.mdc +43 -0
- package/templates/rules/cursor/05-evnict-kit-project-conventions.mdc +63 -0
- package/templates/rules/cursor/README.md +7 -0
- package/templates/skills/evnict-kit-brainstorm/SKILL.md +140 -0
- package/templates/skills/evnict-kit-bug-fix/SKILL.md +108 -0
- package/templates/skills/evnict-kit-checkpoint/SKILL.md +156 -0
- package/templates/skills/evnict-kit-code-review/SKILL.md +158 -0
- package/templates/skills/evnict-kit-coordinate/SKILL.md +274 -0
- package/templates/skills/evnict-kit-create-api/SKILL.md +281 -0
- package/templates/skills/evnict-kit-create-component/SKILL.md +263 -0
- package/templates/skills/evnict-kit-create-page/SKILL.md +247 -0
- package/templates/skills/evnict-kit-database-migration/SKILL.md +164 -0
- package/templates/skills/evnict-kit-doc-postmortem/SKILL.md +93 -0
- package/templates/skills/evnict-kit-finish-branch/SKILL.md +87 -0
- package/templates/skills/evnict-kit-fix-attt/SKILL.md +129 -0
- package/templates/skills/evnict-kit-fix-business-logic/SKILL.md +89 -0
- package/templates/skills/evnict-kit-git-worktrees/SKILL.md +104 -0
- package/templates/skills/evnict-kit-merge-checklist/SKILL.md +108 -0
- package/templates/skills/evnict-kit-onboard/SKILL.md +143 -0
- package/templates/skills/evnict-kit-prompt-standard/SKILL.md +103 -0
- package/templates/skills/evnict-kit-receiving-review/SKILL.md +89 -0
- package/templates/skills/evnict-kit-security-audit/SKILL.md +190 -0
- package/templates/skills/evnict-kit-spec/SKILL.md +237 -0
- package/templates/skills/evnict-kit-tdd/SKILL.md +413 -0
- package/templates/skills/evnict-kit-wiki/SKILL.md +412 -0
- package/templates/wiki/README.md +35 -0
- package/templates/wiki/config.example.yaml +17 -0
- package/templates/wiki/package.json +17 -0
- package/templates/wiki/raw/notes/.gitkeep +1 -0
- package/templates/wiki/scripts/ingest.js +66 -0
- package/templates/workflows/antigravity/evnict-kit-archive-wiki.md +100 -0
- package/templates/workflows/antigravity/evnict-kit-attt.md +100 -0
- package/templates/workflows/antigravity/evnict-kit-bug-fix.md +107 -0
- package/templates/workflows/antigravity/evnict-kit-feature-large.md +393 -0
- package/templates/workflows/antigravity/evnict-kit-feature-small.md +86 -0
- package/templates/workflows/antigravity/evnict-kit-handoff.md +243 -0
- package/templates/workflows/antigravity/evnict-kit-implement.md +247 -0
- package/templates/workflows/antigravity/evnict-kit-init-check.md +76 -0
- package/templates/workflows/antigravity/evnict-kit-init-context.md +58 -0
- package/templates/workflows/antigravity/evnict-kit-init-rules.md +114 -0
- package/templates/workflows/antigravity/evnict-kit-init-wiki.md +80 -0
- package/templates/workflows/antigravity/evnict-kit-plan.md +308 -0
- package/templates/workflows/antigravity/evnict-kit-review.md +53 -0
- package/templates/workflows/antigravity/evnict-kit-spec-archive.md +53 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-archive-feature.md +164 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-query.md +91 -0
- package/templates/workflows/antigravity/evnict-kit-wiki-scan-project.md +272 -0
- package/templates/workflows/claude/README.md +6 -0
- package/templates/workflows/claude/evnict-kit-archive-wiki.md +98 -0
- package/templates/workflows/claude/evnict-kit-attt.md +98 -0
- package/templates/workflows/claude/evnict-kit-bug-fix.md +105 -0
- package/templates/workflows/claude/evnict-kit-feature-large.md +391 -0
- package/templates/workflows/claude/evnict-kit-feature-small.md +84 -0
- package/templates/workflows/claude/evnict-kit-handoff.md +240 -0
- package/templates/workflows/claude/evnict-kit-implement.md +245 -0
- package/templates/workflows/claude/evnict-kit-init-check.md +74 -0
- package/templates/workflows/claude/evnict-kit-init-context.md +56 -0
- package/templates/workflows/claude/evnict-kit-init-rules.md +112 -0
- package/templates/workflows/claude/evnict-kit-init-wiki.md +78 -0
- package/templates/workflows/claude/evnict-kit-plan.md +305 -0
- package/templates/workflows/claude/evnict-kit-review.md +51 -0
- package/templates/workflows/claude/evnict-kit-spec-archive.md +51 -0
- package/templates/workflows/claude/evnict-kit-wiki-archive-feature.md +162 -0
- package/templates/workflows/claude/evnict-kit-wiki-query.md +89 -0
- package/templates/workflows/claude/evnict-kit-wiki-scan-project.md +270 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: evnict-kit-create-component
|
|
3
|
+
description: Tạo UI component chuẩn — check reuse → generate → style → test → a11y. Angular + PrimeNG.
|
|
4
|
+
compatibility: Angular 16+, PrimeNG
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# evnict-kit-create-component — Tạo UI Component
|
|
8
|
+
|
|
9
|
+
## Khi nào dùng
|
|
10
|
+
- Tạo component mới cho feature
|
|
11
|
+
- Tạo shared/reusable component
|
|
12
|
+
- Tạo form component complex
|
|
13
|
+
|
|
14
|
+
## Input Parameters
|
|
15
|
+
- `name` (bắt buộc): Tên component (VD: customer-form, order-table)
|
|
16
|
+
- `type` (optional): page | shared | feature (default: feature)
|
|
17
|
+
- `module` (optional): Module chứa component
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Workflow Steps
|
|
22
|
+
|
|
23
|
+
### Bước 1: Check Reuse — TRƯỚC KHI tạo mới
|
|
24
|
+
1. Tìm trong `shared/components/` → có component tương tự?
|
|
25
|
+
2. Tìm trong PrimeNG → đã có component sẵn?
|
|
26
|
+
3. Tìm trong module hiện tại → có thể extend?
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Scan existing components
|
|
30
|
+
find src/app -name "*.component.ts" | grep -i "{keyword}"
|
|
31
|
+
# Check PrimeNG docs nếu cần
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Nếu tìm thấy → REUSE hoặc EXTEND, KHÔNG tạo mới.
|
|
35
|
+
|
|
36
|
+
### Bước 2: Generate Component
|
|
37
|
+
|
|
38
|
+
#### Xác định vị trí
|
|
39
|
+
| Type | Path |
|
|
40
|
+
|------|------|
|
|
41
|
+
| shared | `src/app/shared/components/{name}/` |
|
|
42
|
+
| feature | `src/app/features/{module}/components/{name}/` |
|
|
43
|
+
| page | `src/app/features/{module}/pages/{name}/` |
|
|
44
|
+
|
|
45
|
+
#### Tạo files
|
|
46
|
+
```
|
|
47
|
+
{name}/
|
|
48
|
+
├── {name}.component.ts
|
|
49
|
+
├── {name}.component.html
|
|
50
|
+
├── {name}.component.scss
|
|
51
|
+
└── {name}.component.spec.ts
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Bước 3: Component Code
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// ✅ Component tiêu chuẩn
|
|
58
|
+
@Component({
|
|
59
|
+
selector: 'app-{name}',
|
|
60
|
+
templateUrl: './{name}.component.html',
|
|
61
|
+
styleUrls: ['./{name}.component.scss']
|
|
62
|
+
})
|
|
63
|
+
export class {Name}Component implements OnInit, OnDestroy {
|
|
64
|
+
// Input/Output
|
|
65
|
+
@Input() data: {Type}[];
|
|
66
|
+
@Output() onAction = new EventEmitter<{Type}>();
|
|
67
|
+
|
|
68
|
+
// State
|
|
69
|
+
loading = false;
|
|
70
|
+
errorMessage = '';
|
|
71
|
+
|
|
72
|
+
// Lifecycle
|
|
73
|
+
private destroy$ = new Subject<void>();
|
|
74
|
+
|
|
75
|
+
constructor(
|
|
76
|
+
private service: {Module}Service,
|
|
77
|
+
private messageService: MessageService
|
|
78
|
+
) {}
|
|
79
|
+
|
|
80
|
+
ngOnInit(): void {
|
|
81
|
+
this.loadData();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ngOnDestroy(): void {
|
|
85
|
+
this.destroy$.next();
|
|
86
|
+
this.destroy$.complete();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Methods
|
|
90
|
+
loadData(): void {
|
|
91
|
+
this.loading = true;
|
|
92
|
+
this.service.getData()
|
|
93
|
+
.pipe(
|
|
94
|
+
takeUntil(this.destroy$),
|
|
95
|
+
finalize(() => this.loading = false)
|
|
96
|
+
)
|
|
97
|
+
.subscribe({
|
|
98
|
+
next: (res) => {
|
|
99
|
+
if (res.status === 0) {
|
|
100
|
+
this.data = res.data;
|
|
101
|
+
} else {
|
|
102
|
+
this.errorMessage = res.message;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
error: (err) => {
|
|
106
|
+
this.messageService.add({
|
|
107
|
+
severity: 'error',
|
|
108
|
+
summary: 'Lỗi',
|
|
109
|
+
detail: 'Không thể tải dữ liệu'
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Bước 4: Template (HTML)
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<!-- ✅ Template chuẩn với PrimeNG -->
|
|
121
|
+
<div class="component-container">
|
|
122
|
+
<!-- Loading state -->
|
|
123
|
+
<div *ngIf="loading" class="loading-container">
|
|
124
|
+
<p-progressSpinner></p-progressSpinner>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<!-- Error state -->
|
|
128
|
+
<p-message *ngIf="errorMessage" severity="error" [text]="errorMessage"></p-message>
|
|
129
|
+
|
|
130
|
+
<!-- Content -->
|
|
131
|
+
<ng-container *ngIf="!loading && !errorMessage">
|
|
132
|
+
<!-- Component content here -->
|
|
133
|
+
</ng-container>
|
|
134
|
+
</div>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Bước 5: Styles (SCSS)
|
|
138
|
+
|
|
139
|
+
```scss
|
|
140
|
+
// ✅ Styles chuẩn
|
|
141
|
+
:host {
|
|
142
|
+
display: block;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.component-container {
|
|
146
|
+
padding: 1rem;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.loading-container {
|
|
150
|
+
display: flex;
|
|
151
|
+
justify-content: center;
|
|
152
|
+
padding: 2rem;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Responsive
|
|
156
|
+
@media (max-width: 768px) {
|
|
157
|
+
.component-container {
|
|
158
|
+
padding: 0.5rem;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Bước 6: Form Component Pattern (nếu là form)
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
// ✅ Reactive Form component
|
|
167
|
+
export class CustomerFormComponent implements OnInit {
|
|
168
|
+
form: FormGroup;
|
|
169
|
+
isEdit = false;
|
|
170
|
+
|
|
171
|
+
constructor(
|
|
172
|
+
private fb: FormBuilder,
|
|
173
|
+
private service: CustomerService,
|
|
174
|
+
private messageService: MessageService
|
|
175
|
+
) {}
|
|
176
|
+
|
|
177
|
+
ngOnInit(): void {
|
|
178
|
+
this.initForm();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private initForm(): void {
|
|
182
|
+
this.form = this.fb.group({
|
|
183
|
+
name: ['', [Validators.required, Validators.maxLength(200)]],
|
|
184
|
+
phone: ['', [Validators.pattern(/^0[0-9]{9}$/)]],
|
|
185
|
+
donViId: [null, Validators.required]
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
onSubmit(): void {
|
|
190
|
+
if (this.form.invalid) {
|
|
191
|
+
this.form.markAllAsTouched();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const dto = this.form.value;
|
|
195
|
+
const action$ = this.isEdit
|
|
196
|
+
? this.service.update(dto.id, dto)
|
|
197
|
+
: this.service.create(dto);
|
|
198
|
+
|
|
199
|
+
action$.subscribe({
|
|
200
|
+
next: (res) => {
|
|
201
|
+
if (res.status === 0) {
|
|
202
|
+
this.messageService.add({
|
|
203
|
+
severity: 'success',
|
|
204
|
+
summary: 'Thành công',
|
|
205
|
+
detail: this.isEdit ? 'Cập nhật thành công' : 'Tạo mới thành công'
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Bước 7: Test
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
describe('{Name}Component', () => {
|
|
218
|
+
let component: {Name}Component;
|
|
219
|
+
let fixture: ComponentFixture<{Name}Component>;
|
|
220
|
+
|
|
221
|
+
beforeEach(async () => {
|
|
222
|
+
await TestBed.configureTestingModule({
|
|
223
|
+
declarations: [{Name}Component],
|
|
224
|
+
imports: [/* required modules */],
|
|
225
|
+
providers: [/* mock services */]
|
|
226
|
+
}).compileComponents();
|
|
227
|
+
|
|
228
|
+
fixture = TestBed.createComponent({Name}Component);
|
|
229
|
+
component = fixture.componentInstance;
|
|
230
|
+
fixture.detectChanges();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should create', () => {
|
|
234
|
+
expect(component).toBeTruthy();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should load data on init', () => { /* ... */ });
|
|
238
|
+
it('should show loading spinner', () => { /* ... */ });
|
|
239
|
+
it('should handle error', () => { /* ... */ });
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Bước 8: A11y Check
|
|
244
|
+
- [ ] Tất cả `<img>` có `alt`
|
|
245
|
+
- [ ] Form fields có `<label>` hoặc `aria-label`
|
|
246
|
+
- [ ] Interactive elements có `tabindex`
|
|
247
|
+
- [ ] Color contrast ≥ 4.5:1
|
|
248
|
+
|
|
249
|
+
### Bước 9: Register Component
|
|
250
|
+
Thêm vào module declarations hoặc shared module exports.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Tiêu chí hoàn thành
|
|
255
|
+
- [ ] Reuse check done — không tạo trùng
|
|
256
|
+
- [ ] Component files tạo đầy đủ (ts, html, scss, spec.ts)
|
|
257
|
+
- [ ] Reactive Forms nếu là form component
|
|
258
|
+
- [ ] Loading/error states handled
|
|
259
|
+
- [ ] RxJS unsubscribe (takeUntil pattern)
|
|
260
|
+
- [ ] Responsive design
|
|
261
|
+
- [ ] A11y check pass
|
|
262
|
+
- [ ] Tests viết và PASS
|
|
263
|
+
- [ ] Registered trong module
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: evnict-kit-create-page
|
|
3
|
+
description: Tạo page mới — module → routing → component → service → loading/error states. Angular lazy-loaded.
|
|
4
|
+
compatibility: Angular 16+, PrimeNG
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# evnict-kit-create-page — Tạo Page Mới
|
|
8
|
+
|
|
9
|
+
## Khi nào dùng
|
|
10
|
+
- Tạo page mới trong feature module
|
|
11
|
+
- Tạo feature module mới với routing
|
|
12
|
+
|
|
13
|
+
## Input Parameters
|
|
14
|
+
- `name` (bắt buộc): Tên page (VD: customer-list, order-detail)
|
|
15
|
+
- `module` (bắt buộc): Module chứa page
|
|
16
|
+
- `route` (optional): Route path (default: based on name)
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Workflow Steps
|
|
21
|
+
|
|
22
|
+
### Bước 1: Kiểm tra module
|
|
23
|
+
|
|
24
|
+
#### Module đã tồn tại?
|
|
25
|
+
```bash
|
|
26
|
+
find src/app/features -name "{module}.module.ts"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
#### Nếu chưa → Tạo module mới
|
|
30
|
+
```
|
|
31
|
+
src/app/features/{module}/
|
|
32
|
+
├── {module}.module.ts
|
|
33
|
+
├── {module}-routing.module.ts
|
|
34
|
+
├── pages/
|
|
35
|
+
│ └── {page-name}/
|
|
36
|
+
├── components/
|
|
37
|
+
├── services/
|
|
38
|
+
└── models/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// {module}.module.ts
|
|
43
|
+
@NgModule({
|
|
44
|
+
declarations: [],
|
|
45
|
+
imports: [
|
|
46
|
+
CommonModule,
|
|
47
|
+
{Module}RoutingModule,
|
|
48
|
+
SharedModule,
|
|
49
|
+
// PrimeNG modules
|
|
50
|
+
TableModule,
|
|
51
|
+
ButtonModule,
|
|
52
|
+
InputTextModule,
|
|
53
|
+
DialogModule
|
|
54
|
+
]
|
|
55
|
+
})
|
|
56
|
+
export class {Module}Module {}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// {module}-routing.module.ts
|
|
61
|
+
const routes: Routes = [
|
|
62
|
+
{ path: '', component: {PageName}Component }
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
@NgModule({
|
|
66
|
+
imports: [RouterModule.forChild(routes)],
|
|
67
|
+
exports: [RouterModule]
|
|
68
|
+
})
|
|
69
|
+
export class {Module}RoutingModule {}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Bước 2: Thêm lazy loading vào app-routing
|
|
73
|
+
```typescript
|
|
74
|
+
// app-routing.module.ts
|
|
75
|
+
{
|
|
76
|
+
path: '{module}',
|
|
77
|
+
loadChildren: () => import('./features/{module}/{module}.module')
|
|
78
|
+
.then(m => m.{Module}Module),
|
|
79
|
+
canActivate: [AuthGuard]
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Bước 3: Tạo page component
|
|
84
|
+
Dùng skill `evnict-kit-create-component` với type=page.
|
|
85
|
+
|
|
86
|
+
#### List Page Pattern
|
|
87
|
+
```typescript
|
|
88
|
+
@Component({
|
|
89
|
+
selector: 'app-{name}-list',
|
|
90
|
+
templateUrl: './{name}-list.component.html'
|
|
91
|
+
})
|
|
92
|
+
export class {Name}ListComponent implements OnInit {
|
|
93
|
+
data: {Type}[] = [];
|
|
94
|
+
totalRecords = 0;
|
|
95
|
+
loading = false;
|
|
96
|
+
keyword = '';
|
|
97
|
+
|
|
98
|
+
// Dialog
|
|
99
|
+
showDialog = false;
|
|
100
|
+
selectedItem: {Type} | null = null;
|
|
101
|
+
|
|
102
|
+
private destroy$ = new Subject<void>();
|
|
103
|
+
|
|
104
|
+
constructor(
|
|
105
|
+
private service: {Module}Service,
|
|
106
|
+
private messageService: MessageService,
|
|
107
|
+
private confirmService: ConfirmationService
|
|
108
|
+
) {}
|
|
109
|
+
|
|
110
|
+
ngOnInit(): void { this.search(); }
|
|
111
|
+
|
|
112
|
+
search(event?: any): void {
|
|
113
|
+
this.loading = true;
|
|
114
|
+
const page = event ? event.first / event.rows : 0;
|
|
115
|
+
const size = event ? event.rows : 20;
|
|
116
|
+
|
|
117
|
+
this.service.search(this.keyword, page, size)
|
|
118
|
+
.pipe(takeUntil(this.destroy$), finalize(() => this.loading = false))
|
|
119
|
+
.subscribe(res => {
|
|
120
|
+
if (res.status === 0) {
|
|
121
|
+
this.data = res.data.content;
|
|
122
|
+
this.totalRecords = res.data.totalElements;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
onCreate(): void {
|
|
128
|
+
this.selectedItem = null;
|
|
129
|
+
this.showDialog = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
onEdit(item: {Type}): void {
|
|
133
|
+
this.selectedItem = { ...item };
|
|
134
|
+
this.showDialog = true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
onDelete(item: {Type}): void {
|
|
138
|
+
this.confirmService.confirm({
|
|
139
|
+
message: 'Bạn có chắc chắn muốn xóa?',
|
|
140
|
+
accept: () => {
|
|
141
|
+
this.service.delete(item.id)
|
|
142
|
+
.pipe(takeUntil(this.destroy$))
|
|
143
|
+
.subscribe(res => {
|
|
144
|
+
if (res.status === 0) {
|
|
145
|
+
this.messageService.add({ severity: 'success', detail: 'Xóa thành công' });
|
|
146
|
+
this.search();
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onSaved(): void {
|
|
154
|
+
this.showDialog = false;
|
|
155
|
+
this.search();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
ngOnDestroy(): void {
|
|
159
|
+
this.destroy$.next();
|
|
160
|
+
this.destroy$.complete();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### List Page Template
|
|
166
|
+
```html
|
|
167
|
+
<div class="page-container">
|
|
168
|
+
<div class="page-header">
|
|
169
|
+
<h2>{{ 'page.{module}.title' | translate }}</h2>
|
|
170
|
+
<button pButton label="{{ 'button.create' | translate }}" icon="pi pi-plus"
|
|
171
|
+
(click)="onCreate()"></button>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div class="search-bar">
|
|
175
|
+
<span class="p-input-icon-left">
|
|
176
|
+
<i class="pi pi-search"></i>
|
|
177
|
+
<input pInputText [(ngModel)]="keyword" (keyup.enter)="search()"
|
|
178
|
+
[placeholder]="'placeholder.search' | translate">
|
|
179
|
+
</span>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<p-table [value]="data" [lazy]="true" [paginator]="true" [rows]="20"
|
|
183
|
+
[totalRecords]="totalRecords" [loading]="loading"
|
|
184
|
+
(onLazyLoad)="search($event)" [responsive]="true" responsiveLayout="scroll">
|
|
185
|
+
<ng-template pTemplate="header">
|
|
186
|
+
<tr>
|
|
187
|
+
<th>{{ 'column.name' | translate }}</th>
|
|
188
|
+
<th>{{ 'column.status' | translate }}</th>
|
|
189
|
+
<th style="width: 120px">{{ 'column.action' | translate }}</th>
|
|
190
|
+
</tr>
|
|
191
|
+
</ng-template>
|
|
192
|
+
<ng-template pTemplate="body" let-item>
|
|
193
|
+
<tr>
|
|
194
|
+
<td>{{ item.name }}</td>
|
|
195
|
+
<td>{{ item.status }}</td>
|
|
196
|
+
<td>
|
|
197
|
+
<button pButton icon="pi pi-pencil" class="p-button-text" (click)="onEdit(item)"></button>
|
|
198
|
+
<button pButton icon="pi pi-trash" class="p-button-text p-button-danger" (click)="onDelete(item)"></button>
|
|
199
|
+
</td>
|
|
200
|
+
</tr>
|
|
201
|
+
</ng-template>
|
|
202
|
+
<ng-template pTemplate="emptymessage">
|
|
203
|
+
<tr><td colspan="3" class="text-center">{{ 'message.noData' | translate }}</td></tr>
|
|
204
|
+
</ng-template>
|
|
205
|
+
</p-table>
|
|
206
|
+
|
|
207
|
+
<!-- Form Dialog -->
|
|
208
|
+
<app-{name}-form *ngIf="showDialog" [visible]="showDialog" [data]="selectedItem"
|
|
209
|
+
(onSave)="onSaved()" (onCancel)="showDialog = false">
|
|
210
|
+
</app-{name}-form>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<p-confirmDialog></p-confirmDialog>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Bước 4: Tạo service (nếu chưa có)
|
|
217
|
+
Dùng pattern từ `04-evnict-kit-frontend-conventions.md`.
|
|
218
|
+
|
|
219
|
+
### Bước 5: Test page
|
|
220
|
+
```typescript
|
|
221
|
+
describe('{Name}ListComponent', () => {
|
|
222
|
+
// Test: load data on init
|
|
223
|
+
// Test: search with keyword
|
|
224
|
+
// Test: pagination
|
|
225
|
+
// Test: create dialog opens
|
|
226
|
+
// Test: delete with confirmation
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Bước 6: Verify
|
|
231
|
+
```bash
|
|
232
|
+
ng test --watch=false
|
|
233
|
+
ng lint
|
|
234
|
+
ng build
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Tiêu chí hoàn thành
|
|
240
|
+
- [ ] Module tạo/update đúng
|
|
241
|
+
- [ ] Routing configured (lazy-loaded)
|
|
242
|
+
- [ ] Page component với loading/error states
|
|
243
|
+
- [ ] Service created/reused
|
|
244
|
+
- [ ] i18n keys added
|
|
245
|
+
- [ ] Responsive design
|
|
246
|
+
- [ ] Tests viết và PASS
|
|
247
|
+
- [ ] Build OK
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: evnict-kit-database-migration
|
|
3
|
+
description: Tạo migration script chuẩn — naming, UP/DOWN scripts, test, hỗ trợ Oracle + SQL Server.
|
|
4
|
+
compatibility: Oracle 19c, SQL Server 2019+, Flyway/Liquibase
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# evnict-kit-database-migration — Database Migration
|
|
8
|
+
|
|
9
|
+
## Khi nào dùng
|
|
10
|
+
- Thêm/sửa/xóa table, column, index
|
|
11
|
+
- Thay đổi schema database qua migration script
|
|
12
|
+
- Tạo rollback script cho thay đổi DB
|
|
13
|
+
|
|
14
|
+
## Input Parameters
|
|
15
|
+
- `action` (bắt buộc): create-table | alter-table | add-index | add-column | drop-column | seed-data
|
|
16
|
+
- `description` (bắt buộc): Mô tả thay đổi
|
|
17
|
+
- `database` (optional): oracle | sqlserver (default: từ config)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Workflow Steps
|
|
22
|
+
|
|
23
|
+
### Bước 1: Xác định thông tin
|
|
24
|
+
Đọc `.evnict/config.yaml` → database type
|
|
25
|
+
Đọc `.agent/rules/05-evnict-kit-project-conventions.md` → RP05 DB conventions
|
|
26
|
+
|
|
27
|
+
### Bước 2: Tạo migration files
|
|
28
|
+
|
|
29
|
+
#### Naming convention
|
|
30
|
+
```
|
|
31
|
+
V{YYYYMMDD}_{seq}__{description}.sql ← UP
|
|
32
|
+
V{YYYYMMDD}_{seq}__{description}_ROLLBACK.sql ← DOWN
|
|
33
|
+
```
|
|
34
|
+
Ví dụ:
|
|
35
|
+
```
|
|
36
|
+
V20260401_001__create_table_customer.sql
|
|
37
|
+
V20260401_001__create_table_customer_ROLLBACK.sql
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Bước 3: Viết UP script
|
|
41
|
+
|
|
42
|
+
#### Oracle — CREATE TABLE
|
|
43
|
+
```sql
|
|
44
|
+
-- V20260401_001__create_table_customer.sql
|
|
45
|
+
CREATE TABLE CUSTOMER (
|
|
46
|
+
ID NUMBER(19) NOT NULL,
|
|
47
|
+
NAME NVARCHAR2(200) NOT NULL,
|
|
48
|
+
PHONE VARCHAR2(20),
|
|
49
|
+
EMAIL VARCHAR2(100),
|
|
50
|
+
DON_VI_ID NUMBER(19) NOT NULL,
|
|
51
|
+
STATUS VARCHAR2(20) DEFAULT 'ACTIVE' NOT NULL,
|
|
52
|
+
IS_DELETED NUMBER(1) DEFAULT 0 NOT NULL,
|
|
53
|
+
CREATED_BY VARCHAR2(50),
|
|
54
|
+
CREATED_DATE TIMESTAMP DEFAULT SYSTIMESTAMP,
|
|
55
|
+
UPDATED_BY VARCHAR2(50),
|
|
56
|
+
UPDATED_DATE TIMESTAMP,
|
|
57
|
+
CONSTRAINT PK_CUSTOMER PRIMARY KEY (ID),
|
|
58
|
+
CONSTRAINT FK_CUSTOMER_DON_VI FOREIGN KEY (DON_VI_ID) REFERENCES DON_VI(ID)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
CREATE SEQUENCE CUSTOMER_SEQ START WITH 1 INCREMENT BY 1;
|
|
62
|
+
|
|
63
|
+
CREATE INDEX IX_CUSTOMER_DON_VI ON CUSTOMER(DON_VI_ID);
|
|
64
|
+
CREATE INDEX IX_CUSTOMER_STATUS ON CUSTOMER(STATUS);
|
|
65
|
+
|
|
66
|
+
COMMENT ON TABLE CUSTOMER IS 'Bảng khách hàng';
|
|
67
|
+
COMMENT ON COLUMN CUSTOMER.NAME IS 'Tên khách hàng';
|
|
68
|
+
COMMENT ON COLUMN CUSTOMER.STATUS IS 'Trạng thái: ACTIVE, INACTIVE, SUSPENDED';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Oracle — ALTER TABLE
|
|
72
|
+
```sql
|
|
73
|
+
-- V20260402_001__add_customer_address.sql
|
|
74
|
+
ALTER TABLE CUSTOMER ADD (
|
|
75
|
+
ADDRESS NVARCHAR2(500),
|
|
76
|
+
DISTRICT_CODE VARCHAR2(10)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
CREATE INDEX IX_CUSTOMER_DISTRICT ON CUSTOMER(DISTRICT_CODE);
|
|
80
|
+
|
|
81
|
+
COMMENT ON COLUMN CUSTOMER.ADDRESS IS 'Địa chỉ khách hàng';
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### SQL Server — CREATE TABLE
|
|
85
|
+
```sql
|
|
86
|
+
-- V20260401_001__create_table_customer.sql
|
|
87
|
+
CREATE TABLE Customer (
|
|
88
|
+
Id BIGINT IDENTITY(1,1) NOT NULL,
|
|
89
|
+
Name NVARCHAR(200) NOT NULL,
|
|
90
|
+
Phone NVARCHAR(20) NULL,
|
|
91
|
+
Email NVARCHAR(100) NULL,
|
|
92
|
+
DonViId BIGINT NOT NULL,
|
|
93
|
+
Status NVARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
|
|
94
|
+
IsDeleted BIT NOT NULL DEFAULT 0,
|
|
95
|
+
CreatedBy NVARCHAR(50) NULL,
|
|
96
|
+
CreatedDate DATETIME2 DEFAULT GETDATE(),
|
|
97
|
+
UpdatedBy NVARCHAR(50) NULL,
|
|
98
|
+
UpdatedDate DATETIME2 NULL,
|
|
99
|
+
CONSTRAINT PK_Customer PRIMARY KEY (Id),
|
|
100
|
+
CONSTRAINT FK_Customer_DonVi FOREIGN KEY (DonViId) REFERENCES DonVi(Id)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
CREATE INDEX IX_Customer_DonVi ON Customer(DonViId);
|
|
104
|
+
CREATE INDEX IX_Customer_Status ON Customer(Status);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Bước 4: Viết ROLLBACK script
|
|
108
|
+
|
|
109
|
+
#### Oracle
|
|
110
|
+
```sql
|
|
111
|
+
-- V20260401_001__create_table_customer_ROLLBACK.sql
|
|
112
|
+
DROP INDEX IX_CUSTOMER_STATUS;
|
|
113
|
+
DROP INDEX IX_CUSTOMER_DON_VI;
|
|
114
|
+
DROP SEQUENCE CUSTOMER_SEQ;
|
|
115
|
+
DROP TABLE CUSTOMER;
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### SQL Server
|
|
119
|
+
```sql
|
|
120
|
+
-- V20260401_001__create_table_customer_ROLLBACK.sql
|
|
121
|
+
DROP INDEX IX_Customer_Status ON Customer;
|
|
122
|
+
DROP INDEX IX_Customer_DonVi ON Customer;
|
|
123
|
+
DROP TABLE Customer;
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Bước 5: Test migration
|
|
127
|
+
```bash
|
|
128
|
+
# Chạy UP
|
|
129
|
+
flyway migrate
|
|
130
|
+
# hoặc chạy script thủ công trong DB client
|
|
131
|
+
|
|
132
|
+
# Verify: table/column tồn tại
|
|
133
|
+
# Oracle
|
|
134
|
+
SELECT * FROM USER_TABLES WHERE TABLE_NAME = 'CUSTOMER';
|
|
135
|
+
SELECT * FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'CUSTOMER';
|
|
136
|
+
|
|
137
|
+
# Chạy ROLLBACK
|
|
138
|
+
# Verify: table/column đã xóa
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Bước 6: KHÔNG làm
|
|
142
|
+
- ❌ KHÔNG sửa DB trực tiếp (phải qua migration script)
|
|
143
|
+
- ❌ KHÔNG DROP TABLE trong production
|
|
144
|
+
- ❌ KHÔNG ALTER column type nếu có data (phải migrate data trước)
|
|
145
|
+
- ❌ KHÔNG hardcode data trong migration (trừ seed data rõ ràng)
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Checklist per migration
|
|
150
|
+
- [ ] UP script chạy thành công
|
|
151
|
+
- [ ] ROLLBACK script chạy thành công
|
|
152
|
+
- [ ] Naming đúng convention: `V{date}_{seq}__{desc}.sql`
|
|
153
|
+
- [ ] Có COMMENT cho table và columns quan trọng
|
|
154
|
+
- [ ] Index cho foreign keys và columns hay query
|
|
155
|
+
- [ ] Audit columns (CREATED_BY, CREATED_DATE, UPDATED_BY, UPDATED_DATE)
|
|
156
|
+
- [ ] Soft delete column nếu cần (IS_DELETED)
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Tiêu chí hoàn thành
|
|
161
|
+
- [ ] UP script tạo và test pass
|
|
162
|
+
- [ ] ROLLBACK script tạo và test pass
|
|
163
|
+
- [ ] Naming convention đúng
|
|
164
|
+
- [ ] Comments và indexes đầy đủ
|