Skip to content

Commit 4d494ad

Browse files
committed
2 parents cb90c8e + 7478f47 commit 4d494ad

File tree

46 files changed

+1296
-314
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1296
-314
lines changed

README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,22 +165,18 @@ Dự án Angular này áp dụng nhiều **design pattern chuẩn** để đảm
165165
- **Guards** (`AuthGuard`, `RoleGuard`) áp dụng strategy để quyết định quyền truy cập.
166166
- **Environments** (`dev`, `staging`, `prod`) chọn cấu hình phù hợp theo môi trường.
167167

168-
### 6. Facade Pattern
169-
- **Router Manager** gom logic routing vào 1 chỗ.
170-
- **NgRx Facade** (nếu sử dụng) để tách component khỏi chi tiết state management.
171-
172-
### 7. Template Pattern
168+
### 6. Template Pattern
173169
- Các **layout** (`header`, `sidebar`, `footer`) định nghĩa khung sẵn, feature module nhúng nội dung vào khu vực content.
174170

175-
### 8. Smart & Dumb Components (Container/Presenter)
171+
### 7. Smart & Dumb Components (Container/Presenter)
176172
- Component **Smart** xử lý logic, data fetching, và state.
177173
- Component **Dumb** nhận `@Input()` và emit `@Output()`, chỉ chịu trách nhiệm hiển thị.
178174

179-
### 9. Decorator Pattern
175+
### 8. Decorator Pattern
180176
- Angular decorators: `@Component`, `@Directive`, `@Pipe`, `@Injectable`.
181177
- Cho phép mở rộng chức năng mà không sửa code gốc.
182178

183-
### 10. DTO Pattern
179+
### 9. DTO Pattern
184180
- Các **model** trong `core/models` quản lý dữ liệu giữa API và component, đảm bảo type safety.
185181

186182
---

cypress/e2e/exercise-create.cy.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1+
/// <reference types="cypress" />
2+
13
describe('Tạo bài tập mới', () => {
24
beforeEach(() => {
35
// Đăng nhập
46
cy.visit('/auth/identity/login');
57
cy.get('#username-input').type('admin');
6-
cy.get('#password-input').type('admin123');
8+
cy.get('#password-input').type('adminRoot123');
79
cy.contains('button', 'Đăng nhập').click();
810

9-
// Check đã vào trang list
11+
// Chọn tab "Bài tập"
12+
cy.contains('a, button, li', 'Bài tập').click();
1013
cy.url().should('include', '/exercise/exercise-layout/list');
1114
});
1215

1316
it('Tạo bài tập thành công', () => {
1417
cy.fixture('exercise-data').then((exercise) => {
15-
// Mở modal
16-
cy.get('app-btn-type1').first().click();
18+
// Click nút "Tạo thủ công"
19+
cy.get('button[data-description="Tạo thủ công"]')
20+
.should('be.visible')
21+
.click();
1722

18-
cy.get('.modal-content').should('be.visible');
19-
cy.contains('h2', 'Tạo bài tập mới').should('exist');
23+
// Đợi modal hiển thị
24+
cy.get('app-exercise-modal').should('be.visible');
2025

2126
// Step 1
2227
cy.get('#title').type(exercise.title);
@@ -35,12 +40,32 @@ describe('Tạo bài tập mới', () => {
3540

3641
// Step 2
3742
cy.get('#description').type(exercise.description);
38-
cy.get('#orgId').type(exercise.orgId);
43+
44+
// // 👉 Nếu có orgId trong data thì mới nhập và chọn, không thì bỏ qua
45+
// if (exercise.orgId && exercise.orgId.trim() !== '') {
46+
// cy.get('#orgSearch').type(exercise.orgId);
47+
48+
// // Chọn tổ chức đầu tiên trong dropdown (force: true)
49+
// cy.get('.dropdown-item').first().click({ force: true });
50+
51+
// // Kiểm tra đã chọn thành công
52+
// cy.get('.selected-org').should('contain.text', exercise.orgId);
53+
// } else {
54+
// cy.log('⚠️ Không có orgId trong fixture, bỏ qua bước chọn tổ chức');
55+
// }
56+
3957
cy.get('#startTime').type(exercise.startTime);
4058
cy.get('#endTime').type(exercise.endTime);
4159
cy.get('#duration').type(exercise.duration.toString());
42-
cy.get('#allowDiscussionId').type(exercise.allowDiscussionId);
43-
cy.get('#resourceIds').type(exercise.resourceIds);
60+
61+
// Nếu có field allowDiscussionId & resourceIds trong FE thì giữ lại
62+
// if (exercise.allowDiscussionId) {
63+
// cy.get('#allowDiscussionId').type(exercise.allowDiscussionId);
64+
// }
65+
// if (exercise.resourceIds) {
66+
// cy.get('#resourceIds').type(exercise.resourceIds);
67+
// }
68+
4469
cy.get('#tags').type(exercise.tags);
4570

4671
if (exercise.allowAiQuestion) {
@@ -49,12 +74,19 @@ describe('Tạo bài tập mới', () => {
4974
});
5075
}
5176

52-
cy.contains('button', 'Tạo mới').click();
77+
// 👉 Click "Tạo mới"
78+
cy.contains('button', 'Tạo mới').click({ force: true });
5379

54-
// ✅ Check modal đóng (không còn class open)
55-
cy.get('.modal-create-overlay').should('not.have.class', 'open');
80+
// // Check modal đóng
81+
// // Ổn định hơn
82+
// cy.get('app-exercise-modal input#title').should('not.exist');
83+
// // hoặc
84+
// cy.get('app-exercise-modal .modal-content').should(
85+
// 'not.have.class',
86+
// 'open'
87+
// );
5688

57-
// Check notification thành công
89+
// Check notification thành công
5890
cy.get('app-notification-card')
5991
.should('contain.text', 'Tạo bài tập thành công!')
6092
.and('be.visible');

cypress/e2e/login-form.cy.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/// <reference types="cypress" />
2+
13
describe('Trang đăng nhập', () => {
24
beforeEach(() => {
35
cy.visit('/auth/identity/login');
@@ -12,10 +14,12 @@ describe('Trang đăng nhập', () => {
1214

1315
it('Đăng nhập thành công với tài khoản hợp lệ', () => {
1416
cy.get('#username-input').type('admin');
15-
cy.get('#password-input').type('admin123');
17+
cy.get('#password-input').type('adminRoot123');
1618
cy.contains('button', 'Đăng nhập').click();
1719

18-
cy.url().should('include', '/exercise/exercise-layout/list');
20+
// ✅ sửa lại URL cho đúng thực tế
21+
cy.url().should('include', '/post-features/post-list');
22+
1923
cy.window().then((win) => {
2024
expect(win.localStorage.getItem('token')).to.exist;
2125
});

src/app/app.routes.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ export const routes: Routes = [
138138
(m) => m.ServiceAndPaymentModule
139139
),
140140
},
141+
{
142+
path: 'codecampus-statistics',
143+
loadChildren: () =>
144+
import('./features/statistics/statistics.module').then(
145+
(m) => m.StatisticsModule
146+
),
147+
},
141148
{
142149
path: 'organization',
143150
loadChildren: () =>

src/app/core/interceptors/handle/error.interceptor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ export const errorInterceptor: HttpInterceptorFn = (req, next) => {
8080
cancelText: 'Hủy',
8181
onConfirm: () => {
8282
router.navigate(['/auth/identity/login']);
83+
localStorage.removeItem('token');
84+
localStorage.removeItem('refreshToken');
8385
},
8486
},
8587
})

src/app/core/models/post.models.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export interface PostADD {
8888
allowComment?: boolean;
8989
postType?: PostType;
9090
fileUrls: string; // CHUẨN THEO BE (lưu ý đánh vần!)
91-
hashtag: string; // nếu muốn gửi dạng mảng
91+
hashtag: string[]; // nếu muốn gửi dạng mảng
9292
fileDocument?: FileDocument | null;
9393
}
9494

@@ -99,7 +99,7 @@ export interface CreatePostRequest {
9999
isPublic: boolean;
100100
allowComment: boolean;
101101
postType: PostType;
102-
hashtag?: string;
102+
hashtag?: string[]; // nếu muốn gửi dạng mảng
103103

104104
fileDocument?: {
105105
files?: File[];
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export type ExerciseStatisticsResponse = {
2+
exerciseId: string;
3+
title: string;
4+
exerciseType: 'QUIZ' | 'CODING';
5+
visibility: boolean;
6+
orgId: string | null;
7+
assignedCount: number;
8+
completedCount: number;
9+
completionRate: number;
10+
submissionCount: number;
11+
passedCount: number;
12+
passRate: number;
13+
avgScore: number;
14+
lastSubmissionAt: string;
15+
};
16+
17+
export type SummaryStatisticsAdmin = {
18+
totalExercises: number;
19+
totalVisibleExercises: number;
20+
totalQuiz: number;
21+
totalCoding: number;
22+
totalAssignments: number;
23+
totalCompletedAssignments: number;
24+
totalSubmissions: number;
25+
totalPassedSubmissions: number;
26+
};

src/app/core/router-manager/horizontal-menu.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export function getNavHorizontalItems(roles: string[]): SidebarItem[] {
2323
path: '/resource-learning/list-resource',
2424
label: 'Kho tài liệu',
2525
icon: 'fas fa-book',
26+
isVisible: !(roles.length !== 0),
2627
},
2728
{
2829
id: 'message',
@@ -33,7 +34,7 @@ export function getNavHorizontalItems(roles: string[]): SidebarItem[] {
3334
},
3435
{
3536
id: 'statistics',
36-
path: '/statistics',
37+
path: '/codecampus-statistics/admin-exercise-statistics',
3738
label: 'Thống kê',
3839
icon: 'fas fa-chart-bar',
3940
isVisible: !roles.includes(auth_lv2[0]),
@@ -50,6 +51,7 @@ export function getNavHorizontalItems(roles: string[]): SidebarItem[] {
5051
path: '/service-and-payment/payment',
5152
label: 'Thanh toán',
5253
icon: 'fas fa-credit-card',
54+
isVisible: !(roles.length !== 0),
5355
},
5456
{
5557
id: 'organization ',
@@ -59,6 +61,7 @@ export function getNavHorizontalItems(roles: string[]): SidebarItem[] {
5961
label: 'Tổ chức',
6062
icon: 'fa-solid fa-building-user',
6163
// isVisible: !roles.includes(auth_lv2[0]),
64+
isVisible: !(roles.length !== 0),
6265
},
6366
];
6467
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { SidebarItem } from '../../models/data-handle';
2+
3+
export function sidebarStatisticsRouter(roles: string[]): SidebarItem[] {
4+
const auth_lv2 = ['ADMIN'];
5+
6+
return [
7+
{
8+
id: 'list-exericse-satistics',
9+
path: '/codecampus-statistics/admin-exercise-statistics',
10+
label: 'Thống kê bài tập',
11+
icon: 'fa-solid fa-file-contract',
12+
isVisible: !roles.includes(auth_lv2[0]),
13+
},
14+
{
15+
id: 'chart-exercise-statistics',
16+
path: '/codecampus-statistics/admin-chart-exercise-statistics',
17+
label: 'Biểu đồ thống kê',
18+
icon: 'fa-solid fa-chart-pie',
19+
isVisible: !roles.includes(auth_lv2[0]),
20+
},
21+
];
22+
}

src/app/core/services/api-service/exercise.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ export class ExerciseService {
123123
tags,
124124
difficulty,
125125
search
126-
)
126+
),
127+
true
127128
);
128129
}
129130

0 commit comments

Comments
 (0)