Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ CodeCampus là một nền tảng trực tuyến được thiết kế để h
## Yêu cầu hệ thống
- Node.js (phiên bản LTS mới nhất)
- npm (được cài đặt cùng với Node.js)
- Angular CLI (phiên bản 19+)
- Angular CLI (phiên bản 20.2.1)

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

Expand Down
6 changes: 6 additions & 0 deletions src/app/core/services/api-service/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ export class UserService {

return this.api.post<ApiResponse<null>>(enpoint, data);
}

deleteUserAccount(accountId: string) {
return this.api.delete<ApiResponse<null>>(
API_CONFIG.ENDPOINTS.DELETE.DELETE_USER_ACCOUNT(accountId)
);
}
}
1 change: 1 addition & 0 deletions src/app/core/services/config-service/api.enpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ export const API_CONFIG = {
DELETE_BLOCK: (blockId: string) => `/org/block/${blockId}`,
REMOVE_MEMBER_FROM_BLOCK: (blockId: string, memberId: string) =>
`/org/block/${blockId}/member/${memberId}`,
DELETE_USER_ACCOUNT: (userId: string) => `/identity/user/${userId}`,
},
},
HEADERS: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
transition: opacity var(--transition-speed) ease,
visibility var(--transition-speed) ease;
z-index: 1000;
pointer-events: none;

&.active {
opacity: 1;
pointer-events: auto;
visibility: visible;
}
}
Expand All @@ -44,10 +46,12 @@
opacity var(--transition-speed) ease;
display: flex;
flex-direction: column;
pointer-events: none;

&.active {
transform: translateY(0);
opacity: 1;
pointer-events: auto;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
></app-dropdown-button>
</div>
<div class="button-user">
<app-button
<!-- <app-button
(onClick)="handleImport()"
[width]="'50px'"
[height]="'50px'"
Expand All @@ -76,9 +76,10 @@
<path d="M12 18v-6" />
<path d="m9 15 3 3 3-3" />
</svg>
</app-button>
</app-button> -->
<!-- Nút Import -->
<app-button
(onClick)="handleImport()"
(onClick)="triggerFileInput()"
[width]="'50px'"
[height]="'50px'"
variant="solid"
Expand All @@ -103,6 +104,16 @@
<path d="m15 15-3-3-3 3" />
</svg>
</app-button>

<!-- Input file ẩn -->
<input
#fileInput
type="file"
accept=".xlsx,.xls"
(change)="onImportExcel($event)"
style="display: none"
/>

<app-button
(onClick)="handleAdd()"
[width]="'50px'"
Expand All @@ -118,16 +129,18 @@
<app-table
[headers]="headers"
[data]="ListUser"
[amountDataPerPage]="10"
[amountDataPerPage]="itemsPerPage"
[needNo]="true"
[needDelete]="true"
[needEdit]="true"
[needViewResult]="false"
[needswitch]="true"
[idSelect]="'userId'"
[onSwitchClick]="handleSwitch"
[switchField]="'status'"
[lockValue]="0"
[openValue]="1"
[onDeleteClick]="openModalDelete"
>
<!-- Avatar + DisplayName + Popup Links -->
<ng-template #cell_displayName let-row let-i="rowIndex">
Expand All @@ -136,16 +149,34 @@
<img
[src]="row.avatarUrl"
alt="avatar"
width="36"
height="36"
style="border-radius: 50%; margin-right: 8px"
width="36px"
height="36px"
style="border-radius: 50%; margin-right: 8px; object-fit: cover"
/>
} @else {
<div
class="avatar"
style="
min-width: 36px;
min-height: 36px;
border-radius: 50%;
margin-right: 8px;
background: #ddd;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 14px;
"
>
{{ row.username?.charAt(0) }}
</div>
}
<span
(click)="onDisplayNameClick(row)"
style="cursor: pointer; font-weight: 600"
>
{{ row.displayName }}
{{ row.displayName ? row.displayName : 'Chưa đặt tên' }}
</span>
</div>
</ng-template>
Expand Down Expand Up @@ -190,9 +221,9 @@
}

<app-pagination
[totalData]="ListUser.length"
[amountDataPerPage]="10"
[currentPageIndex]=" 1"
[totalData]="totalDatas"
[amountDataPerPage]="itemsPerPage"
[currentPageIndex]=" pageIndex"
(onPageChange)="handlePageChange($event)"
></app-pagination>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,34 @@
}
}
}

.btn {
padding: 6px 12px;
border-radius: 6px;
border: none;
cursor: pointer;
transition: all 0.3s ease;

&.primary {
background: var(--button-color);
color: var(--reverse-color-text);
&:hover {
background: oklch(from var(--button-color) calc(l * 0.8) c h);
}
display: flex;
gap: 4px;
}
&.danger {
background: #dc3545;
color: #fff;
&:hover {
background: #a71d2a;
}
}
&.other {
padding: 8px;
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, ElementRef, ViewChild } from '@angular/core';
import { NgClass } from '@angular/common';
import { Store } from '@ngrx/store';

Expand All @@ -20,8 +20,17 @@ import {
} from '../../../../../core/models/user.models';
import { EnumType } from '../../../../../core/models/data-handle';
import { UserService } from '../../../../../core/services/api-service/user.service';
import { clearLoading } from '../../../../../shared/store/loading-state/loading.action';
import {
clearLoading,
setLoading,
} from '../../../../../shared/store/loading-state/loading.action';
import { CreateUserModalComponent } from '../../modal/create-user-modal/create-user-modal.component';
import {
openModalNotification,
sendNotification,
} from '../../../../../shared/utils/notification';
import { OrganizationService } from '../../../../../core/services/api-service/organization.service';
import { ImportMemberResponse } from '../../../../../core/models/organization.model';

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

headers = userHeaders;
Expand Down Expand Up @@ -109,9 +119,11 @@ export class UserListComponent {
// Pagination
pageIndex: number = 1;
itemsPerPage: number = 8;
totalDatas: number = 0;
sortBy: EnumType['sort'] = 'CREATED_AT';
asc: boolean = false;
hasMore = true;
importResult: ImportMemberResponse | null = null;

// Loading
isLoading = false;
Expand All @@ -121,7 +133,11 @@ export class UserListComponent {
role: { value: string; label: string }[] = [];
status: { value: string; label: string }[] = [];

constructor(private userService: UserService, private store: Store) {
constructor(
private userService: UserService,
private store: Store,
private orgService: OrganizationService
) {
// Mock data for role
this.role = [
{ value: 'STUDENT', label: 'Học sinh' },
Expand All @@ -137,6 +153,10 @@ export class UserListComponent {
];
}

triggerFileInput() {
this.fileInput.nativeElement.click();
}

// Lifecycle
ngOnInit(): void {
this.fetchDataListUser();
Expand Down Expand Up @@ -164,7 +184,9 @@ export class UserListComponent {
.subscribe({
next: (res) => {
this.ListUser = res.result.data;
if (this.ListUser.length < this.itemsPerPage) {
this.totalDatas = res.result.totalElements;
this.pageIndex = res.result.currentPage;
if (res.result.currentPage >= res.result.totalPages) {
this.hasMore = false;
}
this.isLoading = false;
Expand All @@ -180,7 +202,8 @@ export class UserListComponent {

// Handlers
handlePageChange(page: number) {
console.log('chuyển trang');
this.pageIndex = page;
this.fetchDataListUser();
}

handleImport = () => {
Expand Down Expand Up @@ -271,4 +294,66 @@ export class UserListComponent {

return values.join(', ');
}

openModalDelete = (id: string | number) => {
openModalNotification(
this.store,
'Xóa người dùng',
'Bạn có chắc chắn xóa người dùng này?',
'Đồng ý',
'Hủy',
() => this.deleteUser(id.toString())
);
};

deleteUser(userId: string) {
return this.userService.deleteUserAccount(userId).subscribe({
next: () => {
sendNotification(this.store, 'Đã xóa', 'Đã xóa người dùng', 'success');
},
error: (err) => {
console.log(err);
},
});
}

downloadTemplate() {
const link = document.createElement('a');
link.href = '/csv/identity_users_import_template.xlsx';
link.download = 'identity_users_import_template.xlsx';
link.click();
}

// Khi chọn file import
onImportExcel(event: Event) {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) return;

Promise.resolve().then(() => {
this.store.dispatch(
setLoading({ isLoading: true, content: 'Đang thêm test case...' })
);
});

this.orgService.importMemberExcel(file).subscribe({
next: (res) => {
this.importResult = res.result;
sendNotification(
this.store,
'Import thành công',
`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>`,
'success'
);
this.store.dispatch(clearLoading());
},
error: (err) => {
alert('Import thất bại!');
console.error(err);
this.store.dispatch(clearLoading());
},
});

// Reset input để chọn lại cùng 1 file lần sau
(event.target as HTMLInputElement).value = '';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import { OrganizationCreateModalComponent } from '../../organization-component/o
import { Router } from '@angular/router';
import { sendNotification } from '../../../../shared/utils/notification';
import { Store } from '@ngrx/store';
import {
clearLoading,
setLoading,
} from '../../../../shared/store/loading-state/loading.action';

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

Promise.resolve().then(() => {
this.store.dispatch(
setLoading({ isLoading: true, content: 'Đang thêm test case...' })
);
});

this.orgService.importMemberExcel(file).subscribe({
next: (res) => {
this.importResult = res.result;
Expand All @@ -157,10 +167,13 @@ export class OrganizationManagementComponent implements OnInit, OnDestroy {
`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>`,
'success'
);

this.store.dispatch(clearLoading());
},
error: (err) => {
alert('Import thất bại!');
console.error(err);
this.store.dispatch(clearLoading());
},
});

Expand Down
Loading
Loading