Skip to content

Commit 6acc357

Browse files
authored
Merge pull request #187 from CapstoneProjectCMC/feature/sap-service-and-payment
Feature/sap service and payment
2 parents 1ab1c7b + b35079b commit 6acc357

File tree

10 files changed

+192
-39
lines changed

10 files changed

+192
-39
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ CodeCampus là một nền tảng trực tuyến được thiết kế để h
118118
## Yêu cầu hệ thống
119119
- Node.js (phiên bản LTS mới nhất)
120120
- npm (được cài đặt cùng với Node.js)
121-
- Angular CLI (phiên bản 19+)
121+
- Angular CLI (phiên bản 20.2.1)
122122

123123
## Cấu trúc dự án (Tổng quan & Minh họa)
124124

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,10 @@ export class UserService {
6363

6464
return this.api.post<ApiResponse<null>>(enpoint, data);
6565
}
66+
67+
deleteUserAccount(accountId: string) {
68+
return this.api.delete<ApiResponse<null>>(
69+
API_CONFIG.ENDPOINTS.DELETE.DELETE_USER_ACCOUNT(accountId)
70+
);
71+
}
6672
}

src/app/core/services/config-service/api.enpoints.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ export const API_CONFIG = {
327327
DELETE_BLOCK: (blockId: string) => `/org/block/${blockId}`,
328328
REMOVE_MEMBER_FROM_BLOCK: (blockId: string, memberId: string) =>
329329
`/org/block/${blockId}/member/${memberId}`,
330+
DELETE_USER_ACCOUNT: (userId: string) => `/identity/user/${userId}`,
330331
},
331332
},
332333
HEADERS: {

src/app/features/admin/user-management/modal/create-user-modal/create-user-modal.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
transition: opacity var(--transition-speed) ease,
2525
visibility var(--transition-speed) ease;
2626
z-index: 1000;
27+
pointer-events: none;
2728

2829
&.active {
2930
opacity: 1;
31+
pointer-events: auto;
3032
visibility: visible;
3133
}
3234
}
@@ -44,10 +46,12 @@
4446
opacity var(--transition-speed) ease;
4547
display: flex;
4648
flex-direction: column;
49+
pointer-events: none;
4750

4851
&.active {
4952
transform: translateY(0);
5053
opacity: 1;
54+
pointer-events: auto;
5155
}
5256
}
5357

src/app/features/admin/user-management/pages/user-list/user-list.html

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
></app-dropdown-button>
5252
</div>
5353
<div class="button-user">
54-
<app-button
54+
<!-- <app-button
5555
(onClick)="handleImport()"
5656
[width]="'50px'"
5757
[height]="'50px'"
@@ -76,9 +76,10 @@
7676
<path d="M12 18v-6" />
7777
<path d="m9 15 3 3 3-3" />
7878
</svg>
79-
</app-button>
79+
</app-button> -->
80+
<!-- Nút Import -->
8081
<app-button
81-
(onClick)="handleImport()"
82+
(onClick)="triggerFileInput()"
8283
[width]="'50px'"
8384
[height]="'50px'"
8485
variant="solid"
@@ -103,6 +104,16 @@
103104
<path d="m15 15-3-3-3 3" />
104105
</svg>
105106
</app-button>
107+
108+
<!-- Input file ẩn -->
109+
<input
110+
#fileInput
111+
type="file"
112+
accept=".xlsx,.xls"
113+
(change)="onImportExcel($event)"
114+
style="display: none"
115+
/>
116+
106117
<app-button
107118
(onClick)="handleAdd()"
108119
[width]="'50px'"
@@ -118,16 +129,18 @@
118129
<app-table
119130
[headers]="headers"
120131
[data]="ListUser"
121-
[amountDataPerPage]="10"
132+
[amountDataPerPage]="itemsPerPage"
122133
[needNo]="true"
123134
[needDelete]="true"
124135
[needEdit]="true"
125136
[needViewResult]="false"
126137
[needswitch]="true"
138+
[idSelect]="'userId'"
127139
[onSwitchClick]="handleSwitch"
128140
[switchField]="'status'"
129141
[lockValue]="0"
130142
[openValue]="1"
143+
[onDeleteClick]="openModalDelete"
131144
>
132145
<!-- Avatar + DisplayName + Popup Links -->
133146
<ng-template #cell_displayName let-row let-i="rowIndex">
@@ -136,16 +149,34 @@
136149
<img
137150
[src]="row.avatarUrl"
138151
alt="avatar"
139-
width="36"
140-
height="36"
141-
style="border-radius: 50%; margin-right: 8px"
152+
width="36px"
153+
height="36px"
154+
style="border-radius: 50%; margin-right: 8px; object-fit: cover"
142155
/>
156+
} @else {
157+
<div
158+
class="avatar"
159+
style="
160+
min-width: 36px;
161+
min-height: 36px;
162+
border-radius: 50%;
163+
margin-right: 8px;
164+
background: #ddd;
165+
display: flex;
166+
align-items: center;
167+
justify-content: center;
168+
font-weight: 600;
169+
font-size: 14px;
170+
"
171+
>
172+
{{ row.username?.charAt(0) }}
173+
</div>
143174
}
144175
<span
145176
(click)="onDisplayNameClick(row)"
146177
style="cursor: pointer; font-weight: 600"
147178
>
148-
{{ row.displayName }}
179+
{{ row.displayName ? row.displayName : 'Chưa đặt tên' }}
149180
</span>
150181
</div>
151182
</ng-template>
@@ -190,9 +221,9 @@
190221
}
191222

192223
<app-pagination
193-
[totalData]="ListUser.length"
194-
[amountDataPerPage]="10"
195-
[currentPageIndex]=" 1"
224+
[totalData]="totalDatas"
225+
[amountDataPerPage]="itemsPerPage"
226+
[currentPageIndex]=" pageIndex"
196227
(onPageChange)="handlePageChange($event)"
197228
></app-pagination>
198229
</div>

src/app/features/admin/user-management/pages/user-list/user-list.scss

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,34 @@
5757
}
5858
}
5959
}
60+
61+
.btn {
62+
padding: 6px 12px;
63+
border-radius: 6px;
64+
border: none;
65+
cursor: pointer;
66+
transition: all 0.3s ease;
67+
68+
&.primary {
69+
background: var(--button-color);
70+
color: var(--reverse-color-text);
71+
&:hover {
72+
background: oklch(from var(--button-color) calc(l * 0.8) c h);
73+
}
74+
display: flex;
75+
gap: 4px;
76+
}
77+
&.danger {
78+
background: #dc3545;
79+
color: #fff;
80+
&:hover {
81+
background: #a71d2a;
82+
}
83+
}
84+
&.other {
85+
padding: 8px;
86+
}
87+
}
6088
}
6189
}
6290
}

src/app/features/admin/user-management/pages/user-list/user-list.ts

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from '@angular/core';
1+
import { Component, ElementRef, ViewChild } from '@angular/core';
22
import { NgClass } from '@angular/common';
33
import { Store } from '@ngrx/store';
44

@@ -20,8 +20,17 @@ import {
2020
} from '../../../../../core/models/user.models';
2121
import { EnumType } from '../../../../../core/models/data-handle';
2222
import { UserService } from '../../../../../core/services/api-service/user.service';
23-
import { clearLoading } from '../../../../../shared/store/loading-state/loading.action';
23+
import {
24+
clearLoading,
25+
setLoading,
26+
} from '../../../../../shared/store/loading-state/loading.action';
2427
import { CreateUserModalComponent } from '../../modal/create-user-modal/create-user-modal.component';
28+
import {
29+
openModalNotification,
30+
sendNotification,
31+
} from '../../../../../shared/utils/notification';
32+
import { OrganizationService } from '../../../../../core/services/api-service/organization.service';
33+
import { ImportMemberResponse } from '../../../../../core/models/organization.model';
2534

2635
@Component({
2736
selector: 'app-user-list',
@@ -41,6 +50,7 @@ import { CreateUserModalComponent } from '../../modal/create-user-modal/create-u
4150
standalone: true,
4251
})
4352
export class UserListComponent {
53+
@ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;
4454
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
4555

4656
headers = userHeaders;
@@ -109,9 +119,11 @@ export class UserListComponent {
109119
// Pagination
110120
pageIndex: number = 1;
111121
itemsPerPage: number = 8;
122+
totalDatas: number = 0;
112123
sortBy: EnumType['sort'] = 'CREATED_AT';
113124
asc: boolean = false;
114125
hasMore = true;
126+
importResult: ImportMemberResponse | null = null;
115127

116128
// Loading
117129
isLoading = false;
@@ -121,7 +133,11 @@ export class UserListComponent {
121133
role: { value: string; label: string }[] = [];
122134
status: { value: string; label: string }[] = [];
123135

124-
constructor(private userService: UserService, private store: Store) {
136+
constructor(
137+
private userService: UserService,
138+
private store: Store,
139+
private orgService: OrganizationService
140+
) {
125141
// Mock data for role
126142
this.role = [
127143
{ value: 'STUDENT', label: 'Học sinh' },
@@ -137,6 +153,10 @@ export class UserListComponent {
137153
];
138154
}
139155

156+
triggerFileInput() {
157+
this.fileInput.nativeElement.click();
158+
}
159+
140160
// Lifecycle
141161
ngOnInit(): void {
142162
this.fetchDataListUser();
@@ -164,7 +184,9 @@ export class UserListComponent {
164184
.subscribe({
165185
next: (res) => {
166186
this.ListUser = res.result.data;
167-
if (this.ListUser.length < this.itemsPerPage) {
187+
this.totalDatas = res.result.totalElements;
188+
this.pageIndex = res.result.currentPage;
189+
if (res.result.currentPage >= res.result.totalPages) {
168190
this.hasMore = false;
169191
}
170192
this.isLoading = false;
@@ -180,7 +202,8 @@ export class UserListComponent {
180202

181203
// Handlers
182204
handlePageChange(page: number) {
183-
console.log('chuyển trang');
205+
this.pageIndex = page;
206+
this.fetchDataListUser();
184207
}
185208

186209
handleImport = () => {
@@ -271,4 +294,66 @@ export class UserListComponent {
271294

272295
return values.join(', ');
273296
}
297+
298+
openModalDelete = (id: string | number) => {
299+
openModalNotification(
300+
this.store,
301+
'Xóa người dùng',
302+
'Bạn có chắc chắn xóa người dùng này?',
303+
'Đồng ý',
304+
'Hủy',
305+
() => this.deleteUser(id.toString())
306+
);
307+
};
308+
309+
deleteUser(userId: string) {
310+
return this.userService.deleteUserAccount(userId).subscribe({
311+
next: () => {
312+
sendNotification(this.store, 'Đã xóa', 'Đã xóa người dùng', 'success');
313+
},
314+
error: (err) => {
315+
console.log(err);
316+
},
317+
});
318+
}
319+
320+
downloadTemplate() {
321+
const link = document.createElement('a');
322+
link.href = '/csv/identity_users_import_template.xlsx';
323+
link.download = 'identity_users_import_template.xlsx';
324+
link.click();
325+
}
326+
327+
// Khi chọn file import
328+
onImportExcel(event: Event) {
329+
const file = (event.target as HTMLInputElement).files?.[0];
330+
if (!file) return;
331+
332+
Promise.resolve().then(() => {
333+
this.store.dispatch(
334+
setLoading({ isLoading: true, content: 'Đang thêm test case...' })
335+
);
336+
});
337+
338+
this.orgService.importMemberExcel(file).subscribe({
339+
next: (res) => {
340+
this.importResult = res.result;
341+
sendNotification(
342+
this.store,
343+
'Import thành công',
344+
`Import hoàn tất:\nTổng: <b>${res.result.total}</b> \nTạo mới: <b>${res.result.created}</b> \nBỏ qua: <b>${res.result.skipped}</b>\nLỗi: <b>${res.result.errors.length}</b>`,
345+
'success'
346+
);
347+
this.store.dispatch(clearLoading());
348+
},
349+
error: (err) => {
350+
alert('Import thất bại!');
351+
console.error(err);
352+
this.store.dispatch(clearLoading());
353+
},
354+
});
355+
356+
// Reset input để chọn lại cùng 1 file lần sau
357+
(event.target as HTMLInputElement).value = '';
358+
}
274359
}

src/app/features/organization/pages/organization-management/organization-management.component.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import { OrganizationCreateModalComponent } from '../../organization-component/o
2424
import { Router } from '@angular/router';
2525
import { sendNotification } from '../../../../shared/utils/notification';
2626
import { Store } from '@ngrx/store';
27+
import {
28+
clearLoading,
29+
setLoading,
30+
} from '../../../../shared/store/loading-state/loading.action';
2731

2832
@Component({
2933
selector: 'app-organization-management',
@@ -148,6 +152,12 @@ export class OrganizationManagementComponent implements OnInit, OnDestroy {
148152
const file = (event.target as HTMLInputElement).files?.[0];
149153
if (!file) return;
150154

155+
Promise.resolve().then(() => {
156+
this.store.dispatch(
157+
setLoading({ isLoading: true, content: 'Đang thêm test case...' })
158+
);
159+
});
160+
151161
this.orgService.importMemberExcel(file).subscribe({
152162
next: (res) => {
153163
this.importResult = res.result;
@@ -157,10 +167,13 @@ export class OrganizationManagementComponent implements OnInit, OnDestroy {
157167
`Import hoàn tất:\nTổng: <b>${res.result.total}</b> \nTạo mới: <b>${res.result.created}</b> \nBỏ qua: <b>${res.result.skipped}</b>\nLỗi: <b>${res.result.errors.length}</b>`,
158168
'success'
159169
);
170+
171+
this.store.dispatch(clearLoading());
160172
},
161173
error: (err) => {
162174
alert('Import thất bại!');
163175
console.error(err);
176+
this.store.dispatch(clearLoading());
164177
},
165178
});
166179

0 commit comments

Comments
 (0)