From 746d2347148e6cd08270a9ef9a568b4fd0a9522a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B4=20Quang=20=C4=90=E1=BB=A9c?= Date: Wed, 3 Sep 2025 05:25:15 +0700 Subject: [PATCH] add import excel --- .../csv/identity_users_import_template.xlsx | Bin 0 -> 53814 bytes src/app/core/constants/menu-router.data.ts | 186 ------------ src/app/core/fake-data/comment.data.ts | 273 ------------------ src/app/core/models/organization.model.ts | 8 + .../api-service/organization.service.ts | 8 + .../services/config-service/api.enpoints.ts | 1 + src/app/core/services/grpc/Playground.proto | 50 ---- .../details-organization.component.html | 129 +++++---- .../details-organization.component.scss | 99 +++++++ .../modal-add-user-to-block.component.ts | 11 +- .../block-details.component.html | 10 +- .../block-details.component.scss | 22 ++ .../block-details/block-details.component.ts | 16 +- .../organization-management.component.html | 18 +- .../organization-management.component.scss | 22 +- .../organization-management.component.ts | 44 ++- .../layout-pages/admin-layout/admin-layout.ts | 5 - .../comment/comment.component.ts | 3 +- 18 files changed, 312 insertions(+), 593 deletions(-) create mode 100644 public/csv/identity_users_import_template.xlsx delete mode 100644 src/app/core/fake-data/comment.data.ts delete mode 100644 src/app/core/services/grpc/Playground.proto diff --git a/public/csv/identity_users_import_template.xlsx b/public/csv/identity_users_import_template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0c9c8a5d030c99afc7bd5bc58373aad50807b142 GIT binary patch literal 53814 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 literal 0 HcmV?d00001 diff --git a/src/app/core/constants/menu-router.data.ts b/src/app/core/constants/menu-router.data.ts index c98dbde8..67dd1f6d 100644 --- a/src/app/core/constants/menu-router.data.ts +++ b/src/app/core/constants/menu-router.data.ts @@ -112,189 +112,3 @@ export const sidebarData: SidebarItem[] = [ ], }, ]; - -export const menuItems: SidebarItem[] = [ - { - id: 'home', - path: 'second/example-using-component-slide/app-menu-layout', - label: 'Home', - icon: 'fas fa-home', - isActive: true, - }, - { - id: 'dashboard', - path: '/second/dashboard', - label: 'Dashboard', - icon: 'fas fa-tachometer-alt', - }, - { - id: 'components', - path: '/second/example-using-component-slide', - label: 'Components', - icon: 'fas fa-code', - children: [ - { - id: 'code-editor', - path: '/second/example-using-component-slide/code-editor', - label: 'Code Editor', - icon: 'fas fa-file-code', - }, - { - id: 'input-button', - path: '/second/example-using-component-slide/input-button', - label: 'Input Button', - icon: 'fas fa-keyboard', - }, - { - id: 'dropdown', - path: '/second/example-using-component-slide/dropdown', - label: 'Dropdown', - icon: 'fas fa-chevron-down', - }, - { - id: 'text-editor', - path: '/second/example-using-component-slide/text-editor', - label: 'Text Editor', - icon: 'fas fa-edit', - }, - { - id: 'card-data', - path: '/second/example-using-component-slide/card-data', - label: 'Card Data', - icon: 'fas fa-id-card', - }, - { - id: 'trending', - path: '/second/example-using-component-slide/trending', - label: 'Trending', - icon: 'fas fa-fire', - }, - ], - }, - { - id: 'auth', - path: '/main/auth', - label: 'Authentication', - icon: 'fas fa-user', - children: [ - { - id: 'login', - path: '/main/auth/login', - label: 'Login', - icon: 'fas fa-sign-in-alt', - }, - { - id: 'register', - path: '/main/auth/register', - label: 'Register', - icon: 'fas fa-user-plus', - }, - ], - }, - { - id: 'post', - path: '/main/post', - label: 'Posts', - icon: 'fas fa-file-pen', - }, -]; - -export const navStudentItems: SidebarItem[] = [ - { - id: 'post', - path: '/post-management/post-list', - label: 'Bài viết', - icon: 'fas fa-newspaper', - }, - { - id: 'exercise', - path: 'exercise/exercise-layout/list', - label: 'Bài tập', - icon: 'fas fa-tasks', - }, - { - id: 'resource', - path: '/resource-management/resource-list', - label: 'Kho tài liệu', - icon: 'fas fa-book', - }, - { - id: 'message', - path: '/message', - label: 'Tin nhắn', - icon: 'fas fa-comments', - children: [ - { - id: 'org', - path: '/message/org', - label: 'Cộng đồng', - icon: 'fas fa-users', - }, - { - id: 'private', - path: '/message/private', - label: 'Nội bộ', - icon: 'fas fa-user-friends', - }, - ], - }, - { - id: 'statistics', - path: '/statistics', - label: 'Thống kê', - icon: 'fas fa-chart-bar', - }, - { - id: 'management', - path: 'management/admin', - label: 'Admin quản lý', - icon: 'fas fa-user-shield', - }, - { - id: 'payment', - path: '/service-and-payment/payment', - label: 'Thanh toán', - icon: 'fas fa-credit-card', - }, - { - id: 'organization ', - path: '/organization/list', - label: 'Tổ chức', - icon: 'fa-solid fa-building-user', - }, -]; - -export const sidebarOrganizations: SidebarItem[] = [ - { - id: 'org-post ', - path: '/organization/details/', - label: 'Bài viết nội bộ', - icon: 'fas fa-newspaper', - }, - { - id: 'org-exercise ', - path: '/organization/exercise', - label: 'Bài tập nội bộ', - icon: 'fa-solid fa-book-open', - }, - { - id: 'grade', - path: '/organization/grade/list', - label: 'Danh sách lớp', - icon: 'fa-solid fa-rectangle-list', - children: [ - { - id: 'org', - path: '/message/org', - label: 'Khối 1', - icon: 'fas fa-users', - }, - { - id: 'private', - path: '/message/private', - label: 'Khối 2', - icon: 'fas fa-users', - }, - ], - }, -]; diff --git a/src/app/core/fake-data/comment.data.ts b/src/app/core/fake-data/comment.data.ts deleted file mode 100644 index eb5970ad..00000000 --- a/src/app/core/fake-data/comment.data.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { avatarUrlDefault } from '../constants/value.constant'; -import { ICommentFilmResponse } from '../models/comment.models'; -const avatarDefault = avatarUrlDefault; - -export const mockComments: ICommentFilmResponse[] = [ - { - id: '1', - parentId: null, - content: 'Bộ phim này thật tuyệt vời, cốt truyện rất lôi cuốn!', - isDeactivated: false, - createdAt: '2025-07-07T10:00:00Z', - updatedAt: '2025-07-07T10:00:00Z', - user: { - id: 'u1', - username: 'hoanganh', - email: 'hoanganh@example.com', - role: 'user', - avatarUrl: avatarDefault, - }, - replies: [ - { - id: '1-1', - parentId: '1', - content: 'Đồng ý! Mình cũng rất thích diễn xuất của nhân vật chính.', - isDeactivated: false, - createdAt: '2025-07-07T10:10:00Z', - updatedAt: '2025-07-07T10:10:00Z', - user: { - id: 'u2', - username: 'linhpham', - avatarUrl: avatarDefault, - }, - }, - { - id: '1-2', - parentId: '1', - content: 'Mình nghĩ đoạn kết hơi hụt hẫng nhưng tổng thể vẫn ok.', - isDeactivated: false, - createdAt: '2025-07-07T10:15:00Z', - updatedAt: '2025-07-07T10:15:00Z', - user: { - id: 'u3', - username: 'davidnguyen', - avatarUrl: avatarDefault, - }, - }, - ], - }, - { - id: '2', - parentId: null, - content: 'Cảnh quay đẹp nhưng kết thúc hơi khó hiểu.', - isDeactivated: false, - createdAt: '2025-07-06T14:20:00Z', - updatedAt: '2025-07-06T14:20:00Z', - user: { - id: 'u3', - username: 'davidnguyen', - avatarUrl: avatarDefault, - }, - replies: [ - { - id: '2-1', - parentId: '2', - content: 'Đúng rồi, mình phải xem lại lần 2 mới hiểu hơn.', - isDeactivated: false, - createdAt: '2025-07-06T15:00:00Z', - updatedAt: '2025-07-06T15:00:00Z', - user: { - id: 'u4', - username: 'minhthuy', - avatarUrl: avatarDefault, - }, - }, - ], - }, - { - id: '3', - parentId: null, - content: 'Có ai biết nhạc nền trong phim này tên gì không?', - isDeactivated: false, - createdAt: '2025-07-05T12:30:00Z', - updatedAt: '2025-07-05T12:30:00Z', - user: { - id: 'u4', - username: 'minhthuy', - avatarUrl: avatarDefault, - }, - replies: [ - { - id: '3-1', - parentId: '3', - content: 'Mình nghĩ là bài "Endless Journey" của Yiruma đó!', - isDeactivated: false, - createdAt: '2025-07-05T12:50:00Z', - updatedAt: '2025-07-05T12:50:00Z', - user: { - id: 'u5', - username: 'tranquang', - avatarUrl: avatarDefault, - }, - }, - ], - }, - { - id: '4', - parentId: null, - content: 'Thấy phim hơi dài, có nhiều đoạn thừa.', - isDeactivated: false, - createdAt: '2025-07-05T15:45:00Z', - updatedAt: '2025-07-05T15:45:00Z', - user: { - id: 'u5', - username: 'tranquang', - avatarUrl: avatarDefault, - }, - replies: [ - { - id: '4-1', - parentId: '4', - content: 'Chuẩn luôn! Nếu cắt bớt chắc hay hơn.', - isDeactivated: false, - createdAt: '2025-07-05T16:00:00Z', - updatedAt: '2025-07-05T16:00:00Z', - user: { - id: 'u6', - username: 'kimanh', - avatarUrl: avatarDefault, - }, - }, - ], - }, - { - id: '5', - parentId: null, - content: 'Phim hay nhưng cần phụ đề tiếng Việt chuẩn hơn.', - isDeactivated: false, - createdAt: '2025-07-04T09:10:00Z', - updatedAt: '2025-07-04T09:10:00Z', - user: { - id: 'u6', - username: 'kimanh', - avatarUrl: avatarDefault, - }, - replies: [ - { - id: '5-1', - parentId: '5', - content: 'Chuẩn rồi! Đôi khi mình cũng không hiểu rõ lời thoại.', - isDeactivated: false, - createdAt: '2025-07-04T09:30:00Z', - updatedAt: '2025-07-04T09:30:00Z', - user: { - id: 'u7', - username: 'tuanle', - avatarUrl: avatarDefault, - }, - }, - ], - }, - { - id: '6', - parentId: null, - content: 'Mình xem đi xem lại mà vẫn xúc động như lần đầu.', - isDeactivated: false, - createdAt: '2025-07-03T19:00:00Z', - updatedAt: '2025-07-03T19:00:00Z', - user: { - id: 'u7', - username: 'tuanle', - avatarUrl: avatarDefault, - }, - replies: [], - }, - { - id: '7', - parentId: null, - content: 'Hiệu ứng hình ảnh đỉnh cao, rất đáng xem!', - isDeactivated: false, - createdAt: '2025-07-03T21:15:00Z', - updatedAt: '2025-07-03T21:15:00Z', - user: { - id: 'u8', - username: 'ngocmai', - avatarUrl: avatarDefault, - }, - replies: [ - { - id: '7-1', - parentId: '7', - content: 'Mình mê luôn hiệu ứng ở cảnh cuối cùng!', - isDeactivated: false, - createdAt: '2025-07-03T21:30:00Z', - updatedAt: '2025-07-03T21:30:00Z', - user: { - id: 'u9', - username: 'longvu', - avatarUrl: avatarDefault, - }, - }, - ], - }, - { - id: '8', - parentId: null, - content: 'Âm thanh và nhạc phim thực sự tuyệt vời!', - isDeactivated: false, - createdAt: '2025-07-02T16:30:00Z', - updatedAt: '2025-07-02T16:30:00Z', - user: { - id: 'u9', - username: 'longvu', - avatarUrl: avatarDefault, - }, - replies: [], - }, - { - id: '9', - parentId: null, - content: 'Có phần hơi bạo lực nhưng tổng thể vẫn ổn.', - isDeactivated: false, - createdAt: '2025-07-02T17:00:00Z', - updatedAt: '2025-07-02T17:00:00Z', - user: { - id: 'u10', - username: 'quynhhoa', - avatarUrl: avatarDefault, - }, - replies: [ - { - id: '9-1', - parentId: '9', - content: 'Ừ, có vài cảnh mình phải tua qua luôn.', - isDeactivated: false, - createdAt: '2025-07-02T17:10:00Z', - updatedAt: '2025-07-02T17:10:00Z', - user: { - id: 'u11', - username: 'hoainam', - avatarUrl: avatarDefault, - }, - }, - ], - }, - { - id: '10', - parentId: null, - content: 'Ai đã xem phần 2 chưa? Có hay bằng phần 1 không?', - isDeactivated: false, - createdAt: '2025-07-01T08:20:00Z', - updatedAt: '2025-07-01T08:20:00Z', - user: { - id: 'u11', - username: 'hoainam', - avatarUrl: avatarDefault, - }, - replies: [ - { - id: '10-1', - parentId: '10', - content: 'Phần 2 mình thấy ổn nhưng hơi thiếu cảm xúc so với phần 1.', - isDeactivated: false, - createdAt: '2025-07-01T08:40:00Z', - updatedAt: '2025-07-01T08:40:00Z', - user: { - id: 'u1', - username: 'hoanganh', - avatarUrl: avatarDefault, - }, - }, - ], - }, -]; diff --git a/src/app/core/models/organization.model.ts b/src/app/core/models/organization.model.ts index d1090366..3489c44b 100644 --- a/src/app/core/models/organization.model.ts +++ b/src/app/core/models/organization.model.ts @@ -99,3 +99,11 @@ export type AddUsersOrgRequest = { defaultRole: string; active: boolean; }; + +//response excel +export type ImportMemberResponse = { + total: number; + created: number; + skipped: number; + errors: string[]; +}; diff --git a/src/app/core/services/api-service/organization.service.ts b/src/app/core/services/api-service/organization.service.ts index 81bfd30d..339c5ddd 100644 --- a/src/app/core/services/api-service/organization.service.ts +++ b/src/app/core/services/api-service/organization.service.ts @@ -8,6 +8,7 @@ import { CreateOrgRequest, EditOrgRequest, FilterOrgs, + ImportMemberResponse, OrganizationInfo, OrganizationResponse, ParamGetAllBlockOfOrg, @@ -116,4 +117,11 @@ export class OrganizationService { API_CONFIG.ENDPOINTS.DELETE.REMOVE_MEMBER_FROM_BLOCK(blockId, memberId) ); } + + importMemberExcel(file: File) { + return this.api.uploadFile>( + API_CONFIG.ENDPOINTS.POST.IMPORT_EXCEL_ADD_MEMBER, + file + ); + } } diff --git a/src/app/core/services/config-service/api.enpoints.ts b/src/app/core/services/config-service/api.enpoints.ts index e606939b..a9c1a40f 100644 --- a/src/app/core/services/config-service/api.enpoints.ts +++ b/src/app/core/services/config-service/api.enpoints.ts @@ -270,6 +270,7 @@ export const API_CONFIG = { BULK_ADD_TO_ORG: (orgId: string) => `/org/${orgId}/members:bulk`, BULK_ADD_TO_BLOCK: (blockId: string) => `/org/block/${blockId}/members:bulk`, + IMPORT_EXCEL_ADD_MEMBER: '/identity/users/import', }, PUT: { EDIT_FILE: (id: string) => `/file/api/FileDocument/edit/${id}`, diff --git a/src/app/core/services/grpc/Playground.proto b/src/app/core/services/grpc/Playground.proto deleted file mode 100644 index e1cfc7e2..00000000 --- a/src/app/core/services/grpc/Playground.proto +++ /dev/null @@ -1,50 +0,0 @@ -syntax = "proto3"; - -package coding.playground; -option java_multiple_files = true; -option java_package = "com.codecampus.coding.grpc.playground"; -option java_outer_classname = "PlaygroundProto"; - -import "google/protobuf/timestamp.proto"; -import "google/protobuf/empty.proto"; - - -message RunRequest { - string language = 1; // "python" | "cpp" | "java" - string source_code = 2; - string stdin = 3; // optional - int32 memory_mb = 4; // default 256 - float cpus = 5; // default 0.5 - int32 time_limit_sec = 6; // default 5 -} - -message RunUpdate { - enum Phase { - PHASE_UNSPECIFIED = 0; - STARTED = 1; - COMPILING = 2; - COMPILE_OUT = 3; - COMPILE_ERR = 4; - RUNNING = 5; - STDOUT = 6; - STDERR = 7; - FINISHED = 8; - ERROR = 9; - } - - Phase phase = 1; - string chunk = 2; // 1 dòng/1 mẩu log/1 block stdout - int32 exit_code = 3; // set khi FINISHED/ERROR - int32 runtime_ms = 4; // set khi FINISHED - int32 memory_kb = 5; // optional, nếu đo được - google.protobuf.Timestamp ts = 6; -} - - -service PlaygroundService { - // Server-streaming: gửi log/STDOUT theo thời gian thực - rpc Run (RunRequest) returns (stream RunUpdate); - - // (Tuỳ chọn) Hủy job nếu client không kịp cancel stream - rpc Cancel (google.protobuf.Empty) returns (google.protobuf.Empty); -} \ No newline at end of file diff --git a/src/app/features/organization/organization-component/details-organization/details-organization.component.html b/src/app/features/organization/organization-component/details-organization/details-organization.component.html index bb3821c9..480d272f 100644 --- a/src/app/features/organization/organization-component/details-organization/details-organization.component.html +++ b/src/app/features/organization/organization-component/details-organization/details-organization.component.html @@ -1,19 +1,6 @@
- -
-
- -
-
-
-
-
-
- -
-
- + {{ m.role }}
+
+ +
Đang tải...
diff --git a/src/app/features/organization/pages/block-details/block-details.component.scss b/src/app/features/organization/pages/block-details/block-details.component.scss index d8d61eb1..6deb77d8 100644 --- a/src/app/features/organization/pages/block-details/block-details.component.scss +++ b/src/app/features/organization/pages/block-details/block-details.component.scss @@ -156,6 +156,28 @@ gap: 5px; } +/* Nút xóa */ +.member-actions { + display: flex; + align-items: center; +} + +.btn-remove { + background: none; + border: none; + font-size: 1rem; + color: var(--text-muted-color); + cursor: pointer; + padding: 8px; + border-radius: 5px; + transition: color 0.2s ease, background 0.2s ease; + + &:hover { + color: #fff; + background: #ef4444; /* Màu đỏ nổi bật khi di chuột */ + } +} + .role-badge .fa-crown { color: gold; } diff --git a/src/app/features/organization/pages/block-details/block-details.component.ts b/src/app/features/organization/pages/block-details/block-details.component.ts index b3553c86..58b37b18 100644 --- a/src/app/features/organization/pages/block-details/block-details.component.ts +++ b/src/app/features/organization/pages/block-details/block-details.component.ts @@ -9,6 +9,8 @@ import { } from '../../../../core/models/organization.model'; import { ModalAddUserToBlockComponent } from '../../organization-component/modal-add-user-to-block/modal-add-user-to-block.component'; import { avatarUrlDefault } from '../../../../core/constants/value.constant'; +import { openModalNotification } from '../../../../shared/utils/notification'; +import { Store } from '@ngrx/store'; @Component({ selector: 'app-block-details', @@ -31,7 +33,8 @@ export class BlockDetailsComponent implements OnInit { constructor( private route: ActivatedRoute, - private orgService: OrganizationService + private orgService: OrganizationService, + private store: Store ) { this.route.paramMap.subscribe((params) => { this.blockId = params.get('blockId') || ''; @@ -84,6 +87,17 @@ export class BlockDetailsComponent implements OnInit { } } + openModalConfirmDelete(memberId: string) { + openModalNotification( + this.store, + 'Xác nhận xóa', + 'Bạn có chắc chắn xóa thành viên này?', + 'Đồng ý', + 'Hủy', + () => this.removeMember(memberId) + ); + } + removeMember(memberId: string) { this.orgService.removeMemberFromBlock(this.blockId, memberId).subscribe({ next: () => { diff --git a/src/app/features/organization/pages/organization-management/organization-management.component.html b/src/app/features/organization/pages/organization-management/organization-management.component.html index d46f639e..1408ef05 100644 --- a/src/app/features/organization/pages/organization-management/organization-management.component.html +++ b/src/app/features/organization/pages/organization-management/organization-management.component.html @@ -9,7 +9,23 @@

Quản lý Tổ chức

placeholder="Tìm kiếm theo tên, email..." />
- + + + + + diff --git a/src/app/features/organization/pages/organization-management/organization-management.component.scss b/src/app/features/organization/pages/organization-management/organization-management.component.scss index 3b034912..0e028f03 100644 --- a/src/app/features/organization/pages/organization-management/organization-management.component.scss +++ b/src/app/features/organization/pages/organization-management/organization-management.component.scss @@ -29,6 +29,13 @@ display: flex; gap: 1rem; align-items: center; + .btn { + white-space: nowrap; + p { + margin: 0; + font-size: 12px; + } + } } } // Search Bar @@ -55,10 +62,10 @@ cursor: pointer; transition: all 0.3s ease; &.primary { - background: #007bff; - color: #fff; + background: var(--button-color); + color: var(--reverse-color-text); &:hover { - background: #0056b3; + background: oklch(from var(--button-color) calc(l * 0.8) c h); } } &.danger { @@ -68,6 +75,9 @@ background: #a71d2a; } } + &.other { + padding: 8px; + } } .org-list { @@ -98,7 +108,7 @@ } .org-card { - background: #fff; + background: var(--surface-color); border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); display: flex; @@ -110,8 +120,8 @@ min-width: 400px; &:hover { - transform: translateY(-5px); - box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12); + transform: translateY(-1px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12); } .card-header { diff --git a/src/app/features/organization/pages/organization-management/organization-management.component.ts b/src/app/features/organization/pages/organization-management/organization-management.component.ts index b4ad9b40..7ce9775f 100644 --- a/src/app/features/organization/pages/organization-management/organization-management.component.ts +++ b/src/app/features/organization/pages/organization-management/organization-management.component.ts @@ -13,7 +13,8 @@ import { Subject, debounceTime, distinctUntilChanged, takeUntil } from 'rxjs'; import { OrganizationService } from '../../../../core/services/api-service/organization.service'; import { CreateOrgRequest, - FilterOrgs, // Import model FilterOrgs + FilterOrgs, + ImportMemberResponse, // Import model FilterOrgs OrganizationResponse, } from '../../../../core/models/organization.model'; import { PaginationComponent } from '../../../../shared/components/fxdonad-shared/pagination/pagination.component'; @@ -21,6 +22,8 @@ import { lottieOptions2 } from '../../../../core/constants/value.constant'; import { LottieComponent } from 'ngx-lottie'; import { OrganizationCreateModalComponent } from '../../organization-component/organization-create-modal/organization-create-modal.component'; import { Router } from '@angular/router'; +import { sendNotification } from '../../../../shared/utils/notification'; +import { Store } from '@ngrx/store'; @Component({ selector: 'app-organization-management', @@ -55,11 +58,13 @@ export class OrganizationManagementComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); createForm!: FormGroup; + importResult: ImportMemberResponse | null = null; constructor( private orgService: OrganizationService, private fb: FormBuilder, - private router: Router + private router: Router, + private store: Store ) {} ngOnInit() { @@ -130,7 +135,40 @@ export class OrganizationManagementComponent implements OnInit, OnDestroy { this.loadOrgs(); } - // --- Các hàm modal giữ nguyên --- + // Nút tải file mẫu + 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; + + 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: ${res.result.total}\nTạo mới: ${res.result.created}\nBỏ qua: ${res.result.skipped}\nLỗi: ${res.result.errors.length}`, + 'success' + ); + }, + error: (err) => { + alert('Import thất bại!'); + console.error(err); + }, + }); + + // Reset input để chọn lại cùng 1 file lần sau + (event.target as HTMLInputElement).value = ''; + } + + // --- Các hàm modal --- openCreateModal() { this.showCreateModal = true; } diff --git a/src/app/layouts/layout-pages/admin-layout/admin-layout.ts b/src/app/layouts/layout-pages/admin-layout/admin-layout.ts index 60aaf4ec..3d9a244a 100644 --- a/src/app/layouts/layout-pages/admin-layout/admin-layout.ts +++ b/src/app/layouts/layout-pages/admin-layout/admin-layout.ts @@ -2,12 +2,7 @@ import { Component, OnInit, SimpleChanges } from '@angular/core'; import { Router, NavigationEnd, RouterOutlet } from '@angular/router'; import { filter } from 'rxjs/operators'; import { HeaderComponent } from '../../../shared/components/my-shared/header/header'; -import { FooterComponent } from '../../../shared/components/my-shared/footer/footer'; import { MenuLayoutComponent } from '../../layout-components/menu/menu-layout.component'; -import { - menuItems, - navStudentItems, -} from '../../../core/constants/menu-router.data'; import { CommonModule, NgIf } from '@angular/common'; import { MainSidebarComponent } from '../../../shared/components/fxdonad-shared/main-sidebar/main-sidebar.component'; import { sidebarData } from '../../../features/admin/menu-router.data'; diff --git a/src/app/shared/components/fxdonad-shared/comment/comment.component.ts b/src/app/shared/components/fxdonad-shared/comment/comment.component.ts index 676d7c20..7eb0f876 100644 --- a/src/app/shared/components/fxdonad-shared/comment/comment.component.ts +++ b/src/app/shared/components/fxdonad-shared/comment/comment.component.ts @@ -13,12 +13,11 @@ import { Output, SimpleChanges, } from '@angular/core'; -import { buildImageUrl } from '../../../utils/BuildUrlFile'; + import { CommentResponse, ICommentFilmResponse, } from '../../../../core/models/comment.models'; -import { mockComments } from '../../../../core/fake-data/comment.data'; import { avatarUrlDefault } from '../../../../core/constants/value.constant'; import { decodeJWT } from '../../../utils/stringProcess'; import { checkAuthenticated } from '../../../utils/userInfo';