diff --git a/src/app/core/models/user.models.ts b/src/app/core/models/user.models.ts index 39d2840a..9c4c2757 100644 --- a/src/app/core/models/user.models.ts +++ b/src/app/core/models/user.models.ts @@ -1,8 +1,7 @@ //ví dụ +export const DEFAULT_BG = 'https://wallpaper.dog/large/10932813.jpg'; export const DEFAULT_AVATAR = - 'https://i.pinimg.com/736x/91/12/02/911202d369ed382e806e246dca39399e.jpg'; -export const DEFAULT_BG = - 'https://i.pinimg.com/736x/2f/5f/0e/2f5f0e56562fbef9ca734f233fa2c0b0.jpg'; + 'https://static.vecteezy.com/system/resources/previews/023/329/545/large_2x/user-profile-shadow-symbol-icon-isolated-illustration-vector.jpg'; export type User = { userId: string; // id của user diff --git a/src/app/core/services/config-service/api.enpoints.ts b/src/app/core/services/config-service/api.enpoints.ts index a34255c7..ff4a250a 100644 --- a/src/app/core/services/config-service/api.enpoints.ts +++ b/src/app/core/services/config-service/api.enpoints.ts @@ -12,7 +12,7 @@ export const version = 'v1'; export const API_CONFIG = { BASE_URLS: { - MAIN_API: environment.IP_SERVER, //chạy ở local thì đổi ip thành IP_SERVER_LOCAL và thêm "+ version" vào chuỗi MAIN_API (sau IP). push lên github nhớ stash commit hoặc không làm thay đổi dòng này. + MAIN_API: environment.IP_SERVER_LOCAL + version, //chạy ở local thì đổi ip thành IP_SERVER_LOCAL và thêm "+ version" vào chuỗi MAIN_API (sau IP). push lên github nhớ stash commit hoặc không làm thay đổi dòng này. SECONDARY_API: '', }, ENDPOINTS: { diff --git a/src/app/features/profile/component/updateform/profile-popup/update-profile.html b/src/app/features/profile/component/updateform/profile-popup/update-profile.html index d2cde969..f4e0ae8f 100644 --- a/src/app/features/profile/component/updateform/profile-popup/update-profile.html +++ b/src/app/features/profile/component/updateform/profile-popup/update-profile.html @@ -1,20 +1,196 @@
@if (!isLoading && (hasError || !user)) { -
- - -

Không có dữ liệu

+
+ + +

Không có dữ liệu

+
+ } @if (hasError!=true ) { +
+
+ +
- } - - @if (hasError!=true ) { -
-
- - +
+
+ } @if (hasError!=true ) { +
+
+ + + +
+ + +
+ + + +
+ + +
+ + + + + + - } - - @if (hasError!=true ) { - + title="Xóa link này" + > + + + + + +
} - - @if (hasError!=true ) { -
- Cập nhật -
- } + + + + @if (isAddingLink) { + + } +
+
+
+ } @if (hasError!=true ) { +
+ Cập nhật +
+ } +
diff --git a/src/app/features/profile/component/updateform/profile-popup/update-profile.ts b/src/app/features/profile/component/updateform/profile-popup/update-profile.ts index 67023582..f3305975 100644 --- a/src/app/features/profile/component/updateform/profile-popup/update-profile.ts +++ b/src/app/features/profile/component/updateform/profile-popup/update-profile.ts @@ -44,8 +44,8 @@ import { TruncatePipe } from '../../../../../shared/pipes/format-view.pipe'; FormsModule, ButtonComponent, LottieComponent, - TruncatePipe -], + TruncatePipe, + ], providers: [provideLottieOptions({ player: () => import('lottie-web') })], schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -115,8 +115,8 @@ export class UpdateProfileComponent { ]; this.gender = [ - { value: 'true', label: 'Nam' }, - { value: 'false', label: 'Nữ' }, + { value: 'false', label: 'Nam' }, + { value: 'true', label: 'Nữ' }, ]; } @@ -147,21 +147,21 @@ export class UpdateProfileComponent { this.originalDob = this.user.dob; if (this.user.dob) { const [day, month, year] = this.user.dob.split('/'); - this.dob = `${year}-${month.padStart(2, '0')}-${day.padStart( - 2, - '0' - )}T00:00`; + this.dob = `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`; } - const genderStr = this.user.gender?.toString(); - this.selectedGender = - this.gender.find((g) => g.value === genderStr) || null; + // Gán giới tính + this.selectedGender = + this.gender.find((g) => g.value === String(this.user.gender)) || null; + console.log('selectedGender on init:', this.selectedGender); this.links = [...(this.user.links || [])]; this.selectedCity = this.user.city || ''; const edu = this.user.education; this.selectedEducation = - this.education.find((g) => g.value === edu) || null; + this.education.find((e) => e.value === this.user.education) || null; + + console.log('selectedEducation on init:', this.selectedEducation); } else { this.hasError = true; } @@ -247,7 +247,9 @@ export class UpdateProfileComponent { const isBioChanged = (this.bio || '') !== (this.user.bio || ''); const isGenderChanged = - (this.selectedGender === 'true') !== this.user.gender; + (this.selectedGender?.value === 'true') !== this.user.gender; + console.log('selectedGender:', this.selectedGender); + console.log('user.gender:', this.user.gender); const isDisplayNameChanged = (this.displayName || '') !== (this.user.displayName || ''); @@ -294,7 +296,7 @@ export class UpdateProfileComponent { this.lastName, formatDateToDDMMYYYY(this.dob), this.bio || '', - this.selectedGender === 'true', + this.selectedGender === 'false' ? false : true, this.displayName, Number(this.selectedEducation?.value ?? 0), this.links, diff --git a/src/app/features/profile/page/personal-profile/personal-profile.html b/src/app/features/profile/page/personal-profile/personal-profile.html index 86627cad..c49c5dcc 100644 --- a/src/app/features/profile/page/personal-profile/personal-profile.html +++ b/src/app/features/profile/page/personal-profile/personal-profile.html @@ -8,7 +8,7 @@ [following]="followingList" > -
+ -
-
+
+
--> + - @if (openedUser) { -
-
-
- - -
-
- } + @if (openedUser) { +
+
+
+ + +
+ } + diff --git a/src/app/features/profile/page/personal-profile/personal-profile.scss b/src/app/features/profile/page/personal-profile/personal-profile.scss index 4c8c86e1..03e5e199 100644 --- a/src/app/features/profile/page/personal-profile/personal-profile.scss +++ b/src/app/features/profile/page/personal-profile/personal-profile.scss @@ -1,6 +1,8 @@ .personal-profile { display: flex; flex-direction: row; + justify-content: center; // căn giữa theo chiều ngang + align-items: center; // căn giữa theo chiều dọc gap: 20px; padding: 20px; background: #f9fafb; @@ -9,155 +11,165 @@ min-height: 100vh; .profile-info { - flex: 1 1 30%; - max-width: 500px; + margin: 0; + padding: 20px; // padding bên trong chính nó + flex: none; + width: 60%; + max-width: 100%; + box-sizing: border-box; } - .learning-process { - flex: 2 1 55%; - display: flex; - flex-direction: column; - gap: 20px; - - .card { - background: #fff; - border-radius: 12px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); - padding: 16px 20px; - display: flex; - flex-direction: column; - gap: 16px; - - .title-nav { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 18px; - font-weight: 600; - color: var(--primary-color); - - a { - font-size: 14px; - color: var(--primary-color); - text-decoration: none; - transition: color 0.2s; - &:hover { - color: var(--primary-color); - } - } - } - } - - .academic-results { - @extend .card; - .results { - display: flex; - justify-content: space-around; - - .academic-results-item { - display: flex; - flex-direction: column; - align-items: center; - font-size: 16px; - color: #555; - - .academic-results-value { - font-size: 24px; - font-weight: bold; - color: var(--primary-color); - border: 3px solid var(--primary-color); - border-radius: 50%; - padding: 12px 20px; - margin-bottom: 8px; - } - } - } - } - - .competition { - @extend .card; - - .competition-item { - display: flex; - justify-content: space-between; - padding: 8px 0; - font-size: 15px; - border-bottom: 1px dashed #eee; - - &:last-child { - border-bottom: none; - } - - .competition-status { - padding: 4px 10px; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - text-transform: capitalize; - - &.ongoing { - background: #fff7e0; - color: #d49100; - } - &.upcoming { - background: #ffeae5; - color: #d93000; - } - &.completed { - background: #e5f6f9; - color: #0a7a8c; - } - } - } - } - - .experience { - @extend .card; - - .list-exercise { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 16px; - } - } - } - - .short-cut { - flex: 1 1 20%; - max-width: 250px; - - .recent-notice { - background: #fff; - border-radius: 12px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); - padding: 16px; - - .title { - font-size: 18px; - font-weight: 600; - color: var(--primary-color); - margin-bottom: 12px; - } - - .notice { - margin-bottom: 8px; - .notice-item { - font-size: 14px; - color: #444; - text-decoration: underline; - cursor: pointer; - &:hover { - color: var(--primary-color); - } - } - } - - .noti-button { - margin-top: 12px; - width: 100%; - } - } - } + // .profile-info { + // // flex: 1 1 30%; + // // max-width: 500px; + // max-width: 100%; + // } + + // .learning-process { + // flex: 2 1 55%; + // display: flex; + // flex-direction: column; + // gap: 20px; + + // .card { + // background: #fff; + // border-radius: 12px; + // box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); + // padding: 16px 20px; + // display: flex; + // flex-direction: column; + // gap: 16px; + + // .title-nav { + // display: flex; + // justify-content: space-between; + // align-items: center; + // font-size: 18px; + // font-weight: 600; + // color: var(--primary-color); + + // a { + // font-size: 14px; + // color: var(--primary-color); + // text-decoration: none; + // transition: color 0.2s; + // &:hover { + // color: var(--primary-color); + // } + // } + // } + // } + + // .academic-results { + // @extend .card; + // .results { + // display: flex; + // justify-content: space-around; + + // .academic-results-item { + // display: flex; + // flex-direction: column; + // align-items: center; + // font-size: 16px; + // color: #555; + + // .academic-results-value { + // font-size: 24px; + // font-weight: bold; + // color: var(--primary-color); + // border: 3px solid var(--primary-color); + // border-radius: 50%; + // padding: 12px 20px; + // margin-bottom: 8px; + // } + // } + // } + // } + + // .competition { + // @extend .card; + + // .competition-item { + // display: flex; + // justify-content: space-between; + // padding: 8px 0; + // font-size: 15px; + // border-bottom: 1px dashed #eee; + + // &:last-child { + // border-bottom: none; + // } + + // .competition-status { + // padding: 4px 10px; + // border-radius: 6px; + // font-size: 13px; + // font-weight: 500; + // text-transform: capitalize; + + // &.ongoing { + // background: #fff7e0; + // color: #d49100; + // } + // &.upcoming { + // background: #ffeae5; + // color: #d93000; + // } + // &.completed { + // background: #e5f6f9; + // color: #0a7a8c; + // } + // } + // } + // } + + // .experience { + // @extend .card; + + // .list-exercise { + // display: grid; + // grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + // gap: 16px; + // } + // } + // } + + // .short-cut { + // flex: 1 1 20%; + // max-width: 250px; + + // .recent-notice { + // background: #fff; + // border-radius: 12px; + // box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); + // padding: 16px; + + // .title { + // font-size: 18px; + // font-weight: 600; + // color: var(--primary-color); + // margin-bottom: 12px; + // } + + // .notice { + // margin-bottom: 8px; + // .notice-item { + // font-size: 14px; + // color: #444; + // text-decoration: underline; + // cursor: pointer; + // &:hover { + // color: var(--primary-color); + // } + // } + // } + + // .noti-button { + // margin-top: 12px; + // width: 100%; + // } + // } + // } .profile-popup-overlay { position: fixed; // cố định toàn màn hình top: 0; diff --git a/src/app/features/profile/page/personal-profile/personal-profile.ts b/src/app/features/profile/page/personal-profile/personal-profile.ts index 99bf8f6c..9070fb74 100644 --- a/src/app/features/profile/page/personal-profile/personal-profile.ts +++ b/src/app/features/profile/page/personal-profile/personal-profile.ts @@ -30,10 +30,10 @@ import { ButtonComponent } from '../../../../shared/components/my-shared/button/ ProfilePopupComponent, NgClass, UpdateProfileComponent, - SkeletonLoadingComponent, - CardExcerciseComponent, - ButtonComponent -], + // SkeletonLoadingComponent, + // CardExcerciseComponent, + // ButtonComponent + ], standalone: true, }) export class PersonalProfileComponent { diff --git a/src/app/features/resource-learning/modal/popup-update/resource-edit-popup.component.html b/src/app/features/resource-learning/modal/popup-update/resource-edit-popup.component.html index 75e66e28..c92f0a05 100644 --- a/src/app/features/resource-learning/modal/popup-update/resource-edit-popup.component.html +++ b/src/app/features/resource-learning/modal/popup-update/resource-edit-popup.component.html @@ -6,49 +6,48 @@

Cập nhật Resource

@if (!isLoading && resource) { - + + } - - - @if (isLoading) { -
Đang xử lý...
- } - @if (errorMessage) { -
{{ errorMessage }}
- } + + + @if (isLoading) { +
Đang xử lý...
+ } @if (errorMessage) { +
{{ errorMessage }}
+ } + diff --git a/src/app/features/resource-learning/pages/resource-create/resource-create.ts b/src/app/features/resource-learning/pages/resource-create/resource-create.ts index 2fdd72e3..7494a25c 100644 --- a/src/app/features/resource-learning/pages/resource-create/resource-create.ts +++ b/src/app/features/resource-learning/pages/resource-create/resource-create.ts @@ -16,7 +16,10 @@ import { HtmlToMdService } from '../../../../shared/utils/HTMLtoMarkDown'; import { Router } from '@angular/router'; import { ResourceService } from '../../../../core/services/api-service/resource.service'; import { sendNotification } from '../../../../shared/utils/notification'; -import { clearLoading } from '../../../../shared/store/loading-state/loading.action'; +import { + clearLoading, + setLoading, +} from '../../../../shared/store/loading-state/loading.action'; import { Store } from '@ngrx/store'; import { InputComponent } from '../../../../shared/components/fxdonad-shared/input/input'; import { decodeJWT } from '../../../../shared/utils/stringProcess'; @@ -134,6 +137,12 @@ export class ResourceCreatePageComponent { isTextbook, isLectureVideo, }; + this.store.dispatch( + setLoading({ + isLoading: true, + content: 'Đang tạo tài nguyên, xin chờ...', + }) + ); this.resourceService.addResource(postData).subscribe({ next: (res) => { diff --git a/src/app/features/resource-learning/pages/resource-detail/resource-detail.html b/src/app/features/resource-learning/pages/resource-detail/resource-detail.html index 32fa08d8..9746357b 100644 --- a/src/app/features/resource-learning/pages/resource-detail/resource-detail.html +++ b/src/app/features/resource-learning/pages/resource-detail/resource-detail.html @@ -14,7 +14,7 @@
Avatar
@@ -52,18 +52,30 @@ @if (isDocument) {
+ @if (resource.fileName.endsWith('.pdf')) { + + } @else { + +
+

Tệp này không thể hiển thị trực tiếp. Tệp đã được tải xuống.

+ + Tải xuống {{ resource.fileName }} + +
+ } +
} + @if (isImage) {
@@ -109,7 +121,7 @@
diff --git a/src/app/features/resource-learning/pages/resource-detail/resource-detail.ts b/src/app/features/resource-learning/pages/resource-detail/resource-detail.ts index 73c4e8ca..b3c30513 100644 --- a/src/app/features/resource-learning/pages/resource-detail/resource-detail.ts +++ b/src/app/features/resource-learning/pages/resource-detail/resource-detail.ts @@ -18,6 +18,7 @@ import { } from '../../../../shared/store/loading-state/loading.action'; import { sendNotification } from '../../../../shared/utils/notification'; import { MarkdownModule } from 'ngx-markdown'; +import { DEFAULT_AVATAR } from '../../../../core/models/user.models'; @Component({ selector: 'app-resource-detail', @@ -35,6 +36,8 @@ import { MarkdownModule } from 'ngx-markdown'; standalone: true, }) export class ResourceDetail implements OnInit { + avatarDefault = DEFAULT_AVATAR; + [x: string]: any; resourceId: string | null = null; resource!: MediaResource; diff --git a/src/app/features/resource-learning/pages/resource-list/resource-list.html b/src/app/features/resource-learning/pages/resource-list/resource-list.html index de4f69da..78edec9d 100644 --- a/src/app/features/resource-learning/pages/resource-list/resource-list.html +++ b/src/app/features/resource-learning/pages/resource-list/resource-list.html @@ -3,7 +3,7 @@
+ > +
- @if (!isLoading && resources) { -
- @for (resource of resources; track resource) { -
- -
- } -
- } - - @if (isLoading ) { -
- @if (isLoading) { - - } + @if (!isLoading && filteredResources) { +
+ @for (resource of filteredResources; track resource) { +
+
+ } +
+ } @if (isLoading ) { +
+ +
}
+ @if (selectedResourceId) { - + } diff --git a/src/app/features/resource-learning/pages/resource-list/resource-list.ts b/src/app/features/resource-learning/pages/resource-list/resource-list.ts index 163bc0b3..23a198a9 100644 --- a/src/app/features/resource-learning/pages/resource-list/resource-list.ts +++ b/src/app/features/resource-learning/pages/resource-list/resource-list.ts @@ -30,11 +30,12 @@ import { ScrollEndDirective } from '../../../../shared/directives/scroll-end.dir SkeletonLoadingComponent, ResourceCardComponent, ResourceEditPopupComponent, - ScrollEndDirective -], + ScrollEndDirective, + ], }) export class ResourceListComponent { resources: MediaResource[] = []; + filteredResources: MediaResource[] = []; // danh sách sau khi search trendingData: TrendingItem[] = [ { name: 'Angular', views: 15000 }, @@ -69,7 +70,6 @@ export class ResourceListComponent { { value: '4', label: 'python' }, ]; - // Mock data for status this.status = [ { value: '0', label: 'Reject' }, { value: '1', label: 'Accepted' }, @@ -81,7 +81,6 @@ export class ResourceListComponent { this.fetchDataResource(); } - // ================== Fetch ================== // ================== Fetch ================== fetchDataResource(append: boolean = false) { this.isLoadingMore = append; @@ -94,10 +93,9 @@ export class ResourceListComponent { const newData = res.result.data; const { currentPage, totalPages } = res.result; - // Gán data this.resources = append ? [...this.resources, ...newData] : newData; + this.filteredResources = [...this.resources]; // gán mặc định cho search - // Kiểm tra còn trang tiếp theo không this.hasMore = currentPage < totalPages; this.isLoading = false; @@ -116,7 +114,7 @@ export class ResourceListComponent { if (this.isLoadingMore || !this.hasMore) return; this.pageIndex++; - this.fetchDataResource(true); // append thêm dữ liệu + this.fetchDataResource(true); } // ================== Delete ================== @@ -130,18 +128,18 @@ export class ResourceListComponent { ); this.resourceService.deleteResourceById(resourceId).subscribe({ - next: (res) => { - // Xoá resource trong danh sách hiện tại (vì API chỉ trả string, không trả lại list mới) + next: () => { this.resources = this.resources.filter((r) => r.id !== resourceId); + this.filteredResources = this.filteredResources.filter( + (r) => r.id !== resourceId + ); - // Kiểm tra còn item cho phân trang không if (this.resources.length < this.itemsPerPage) { this.hasMore = false; } this.isLoading = false; this.store.dispatch(clearLoading()); - // Optional: Hiện thông báo xoá thành công sendNotification( this.store, 'Đã xóa tài nguyên', @@ -178,8 +176,15 @@ export class ResourceListComponent { // ================== Input & Filter ================== handleInputChange(value: string | number): void { - this.resourcename = value.toString(); - console.log('Input changed:', this.resourcename); + this.resourcename = value.toString().trim().toLowerCase(); + if (!this.resourcename) { + this.filteredResources = [...this.resources]; + return; + } + + this.filteredResources = this.resources.filter((r) => + r.fileName?.toLowerCase().includes(this.resourcename) + ); } // ================== Actions ================== diff --git a/src/app/features/statistics/pages/user-payment-statistic/user-payment-statistic.component.html b/src/app/features/statistics/pages/user-payment-statistic/user-payment-statistic.component.html index a7d281a2..50ac7b02 100644 --- a/src/app/features/statistics/pages/user-payment-statistic/user-payment-statistic.component.html +++ b/src/app/features/statistics/pages/user-payment-statistic/user-payment-statistic.component.html @@ -39,13 +39,37 @@

Thống kê tiền nạp

+
- + [seriesData]="barChartSeries" + [title]=" + 'Thống kê Nạp & Mua theo ngày trong tháng ' + + selectedMonth + + '/' + + selectedYear + " + [type]="'bar'" + > +
+ + +
+
+
{ - item.day; - console.log('Item day:', item.day); - return item.day; - }); + const categories = data.map((item) => item.day); - const seriesData = [ + // Dữ liệu cho Bar chart (Nạp + Mua) + const barSeriesData = [ { - name: 'Deposit', + name: 'Tiền nạp', data: data.map((item) => item.depositAmount), }, { - name: 'Purchase', + name: 'Tiền mua', data: data.map((item) => item.purchaseAmount), }, + ]; + + // Dữ liệu cho Line chart (Wallet) + const lineSeriesData = [ { - name: 'Wallet Balance', + name: 'Số dư ví', data: data.map((item) => item.walletBalance), }, ]; - console.log('categories:', categories); - console.log('seriesData:', seriesData); - return { categories, seriesData }; + return { categories, barSeriesData, lineSeriesData }; } + // table tableDepositHeaders = [ { label: 'Ngày', value: 'day' }, diff --git a/src/app/features/student-statistic/test-statistic/student-statistic.component.html b/src/app/features/student-statistic/test-statistic/student-statistic.component.html index 713db50a..a34d494f 100644 --- a/src/app/features/student-statistic/test-statistic/student-statistic.component.html +++ b/src/app/features/student-statistic/test-statistic/student-statistic.component.html @@ -112,7 +112,6 @@ ] } ]" - [height]="350" [chartTitle]="'Tỷ lệ hoàn thành bài của học sinh trong tổ chức'" > diff --git a/src/app/shared/components/my-shared/line-chart/line-chart.ts b/src/app/shared/components/my-shared/line-chart/line-chart.ts index 5bc10440..9342128c 100644 --- a/src/app/shared/components/my-shared/line-chart/line-chart.ts +++ b/src/app/shared/components/my-shared/line-chart/line-chart.ts @@ -19,7 +19,6 @@ import { ApexFill, } from 'ng-apexcharts'; - export type ChartOptions = { series: ApexAxisChartSeries; chart: ApexChart; diff --git a/src/app/shared/components/my-shared/multi-chart/multi-chart.html b/src/app/shared/components/my-shared/multi-chart/multi-chart.html new file mode 100644 index 00000000..7e4177fc --- /dev/null +++ b/src/app/shared/components/my-shared/multi-chart/multi-chart.html @@ -0,0 +1,25 @@ +
+ @if (this.title) { +
{{ title }}
+ } @if (chartOptions.series.length && chartOptions.xaxis.categories.length) { +
+ +
+ } @else { +
+ Lỗi: Thiếu dữ liệu đầu vào bắt buộc hoặc dữ liệu không hợp lệ để hiển thị + biểu đồ. +
+ } +
diff --git a/src/app/shared/components/my-shared/multi-chart/multi-chart.scss b/src/app/shared/components/my-shared/multi-chart/multi-chart.scss new file mode 100644 index 00000000..998491fc --- /dev/null +++ b/src/app/shared/components/my-shared/multi-chart/multi-chart.scss @@ -0,0 +1,20 @@ +.chart-container { + width: 100%; +} + +.chart-title { + text-align: center; + font-weight: bold; + margin-bottom: 12px; +} + +.chart-body { + width: 100%; + height: 400px; // 👈 đặt chiều cao rõ ràng +} + +apx-chart { + display: block; + width: 100% !important; + height: 100% !important; +} diff --git a/src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.ts b/src/app/shared/components/my-shared/multi-chart/multi-chart.ts similarity index 54% rename from src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.ts rename to src/app/shared/components/my-shared/multi-chart/multi-chart.ts index a0e20ed6..091df66e 100644 --- a/src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.ts +++ b/src/app/shared/components/my-shared/multi-chart/multi-chart.ts @@ -17,10 +17,12 @@ import { ApexXAxis, ApexFill, ApexTooltip, + ApexTitleSubtitle, NgApexchartsModule, } from 'ng-apexcharts'; export type ChartOptions = { + responsive: any; series: ApexAxisChartSeries; chart: ApexChart; dataLabels: ApexDataLabels; @@ -31,34 +33,40 @@ export type ChartOptions = { tooltip: ApexTooltip; stroke: ApexStroke; legend: ApexLegend; + title: ApexTitleSubtitle; }; @Component({ - selector: 'app-multi-line-chart', - templateUrl: './multi-line-chart.html', - styleUrls: ['./multi-line-chart.scss'], + selector: 'app-multi-chart', + templateUrl: './multi-chart.html', + styleUrls: ['./multi-chart.scss'], imports: [NgApexchartsModule], standalone: true, }) -export class MultiLineChartComponent implements OnChanges { +export class MultiChartComponent implements OnChanges { @ViewChild('chart') chart!: ChartComponent; - // ✅ Nhận categories và series động từ bên ngoài + // ✅ Nhận đầu vào động @Input() categories: string[] = []; @Input() seriesData: ApexAxisChartSeries = []; + @Input() title: string = ''; + @Input() type: 'bar' | 'line' = 'bar'; // 👈 loại biểu đồ public chartOptions: ChartOptions = { series: [], chart: { - type: 'bar', - height: 350, + type: 'bar', // mặc định + height: '100%', // 👈 thay vì 350 fix cứng + width: '100%', // 👈 ép full width + zoom: { + enabled: false, + }, }, dataLabels: { enabled: true, }, stroke: { - curve: 'smooth', - width: 2, + curve: 'smooth', // 👈 chỉ giữ curve }, xaxis: { categories: [], @@ -70,9 +78,7 @@ export class MultiLineChartComponent implements OnChanges { }, tooltip: { y: { - formatter: function (val: number) { - return val + ' VND'; - }, + formatter: (val: number) => val + ' VND', }, }, legend: { @@ -82,11 +88,13 @@ export class MultiLineChartComponent implements OnChanges { opacity: 1, }, plotOptions: { - bar: { - columnWidth: '55%', - borderRadius: 5, - }, + bar: {}, // 👈 để trống, không set width/border + }, + title: { + text: '', + align: 'center', }, + responsive: undefined, }; ngOnChanges(changes: SimpleChanges): void { @@ -94,9 +102,19 @@ export class MultiLineChartComponent implements OnChanges { } private updateChart() { + // cập nhật loại chart + this.chartOptions.chart.type = this.type; + + // nếu là line thì bỏ plotOptions.bar đi + if (this.type === 'line') { + this.chartOptions.plotOptions = {} as ApexPlotOptions; + } + this.chartOptions.series = this.seriesData; - this.chartOptions.xaxis = { - categories: this.categories, + this.chartOptions.xaxis = { categories: this.categories }; + this.chartOptions.title = { + text: this.title, + align: 'center', }; } } diff --git a/src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.html b/src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.html deleted file mode 100644 index 0f656b37..00000000 --- a/src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.html +++ /dev/null @@ -1,14 +0,0 @@ -
- -
diff --git a/src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.scss b/src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.scss deleted file mode 100644 index 6526bd93..00000000 --- a/src/app/shared/components/my-shared/multi-line-chart/multi-line-chart.scss +++ /dev/null @@ -1,3 +0,0 @@ -#chart { - width: 90vw; -} diff --git a/src/app/shared/components/my-shared/profile-popup/profile-popup.ts b/src/app/shared/components/my-shared/profile-popup/profile-popup.ts index a92f03f1..c407a0a8 100644 --- a/src/app/shared/components/my-shared/profile-popup/profile-popup.ts +++ b/src/app/shared/components/my-shared/profile-popup/profile-popup.ts @@ -5,6 +5,7 @@ import { Router } from '@angular/router'; import { formatDate } from '../../../utils/stringProcess'; import { ButtonComponent } from '../button/button.component'; import { + DEFAULT_AVATAR, DEFAULT_BG, follow, SearchUserProfileResponse, @@ -26,7 +27,7 @@ export class ProfilePopupComponent { @Input() variant: 'personal' | 'other' | 'popup' = 'popup'; @Input() onClickEdit?: () => void; showFollowPopup = false; - avatarDefault = avatarUrlDefault; + avatarDefault = DEFAULT_AVATAR; backgroundDefault = DEFAULT_BG; formatDate(time: Date) { diff --git a/src/app/shared/components/my-shared/resource-card/resource-card.html b/src/app/shared/components/my-shared/resource-card/resource-card.html index 42c186d3..62030835 100644 --- a/src/app/shared/components/my-shared/resource-card/resource-card.html +++ b/src/app/shared/components/my-shared/resource-card/resource-card.html @@ -1,68 +1,44 @@
@if (isFileType('video')) { - - } - - @if (isFileType('image')) { - - } - - @if (isFileType('document')) { -
-
- @if (resource.fileType.includes('pdf')) { - - } - @if (resource.fileType.includes('word')) { - - } - @if (!isSpecificDocumentType()) { - - } -
-
- } - - @if (isFileType('document') && resource.fileType.includes('pdf')) { - - } - - @if (isFileType('image') || isFileType('video')) { -
- @if (isFileType('video') && resource.duration) { - - {{ resource.duration }} - + + } @if (isFileType('image')) { + + } @if (isFileType('document')) { +
+
+ @if (resource.fileType.includes('pdf')) { + + } @if (resource.fileType.includes('word')) { + + } @if (!isSpecificDocumentType()) { + } -
- {{ resource.viewCount || 0 }} -
-
- {{ resource.rating || 0 }} -
+
+ } @if (isFileType('document') && resource.fileType.includes('pdf')) { + + } @if (isFileType('image') || isFileType('video')) { +
+ @if (isFileType('video') && resource.duration) { + {{ resource.duration }} + } +
+ {{ resource.viewCount || 0 }} +
+
+ {{ resource.rating || 0 }} +
+
}
@@ -71,33 +47,31 @@

{{ resource.fileName }}

- {{ resource.userProfile.displayName }} -
- {{ formatDate(resource.createdAt) }} + /> + {{ resource.userProfile.displayName }}
+ {{ formatDate(resource.createdAt) }} +
- @if (resource.tags.length > 0) { -
- @for (tag of resource.tags; track tag) { - - {{ tag.name }} - - } -
+ @if (resource.tags.length > 0) { +
+ @for (tag of resource.tags; track tag) { + {{ tag.name }} }
- @if (showControls && isActionActive) { -
- - -
}
+ @if (showControls && isActionActive) { +
+ + +
+ } +
diff --git a/src/app/shared/components/my-shared/resource-card/resource-card.ts b/src/app/shared/components/my-shared/resource-card/resource-card.ts index b5713973..b29c9ea2 100644 --- a/src/app/shared/components/my-shared/resource-card/resource-card.ts +++ b/src/app/shared/components/my-shared/resource-card/resource-card.ts @@ -11,6 +11,10 @@ import { MediaResource } from '../../../../core/models/resource.model'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { activeForMyContent } from '../../../utils/authenRoleActions'; import { getUserRoles } from '../../../utils/userInfo'; +import { + DEFAULT_AVATAR, + DEFAULT_BG, +} from '../../../../core/models/user.models'; @Component({ selector: 'app-resource-card', @@ -26,6 +30,8 @@ export class ResourceCardComponent implements OnChanges { @Output() main = new EventEmitter(); @Output() edit = new EventEmitter(); @Output() delete = new EventEmitter(); + avatarDefault = DEFAULT_AVATAR; + backgroundDefault = DEFAULT_BG; safeUrl!: SafeResourceUrl; roles = getUserRoles();