eiu-app-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +88 -0
  2. package/esm2020/eiu-app-kit.mjs +5 -0
  3. package/esm2020/lib/app-launcher/eiu-app-launcher-menu.model.mjs +2 -0
  4. package/esm2020/lib/app-launcher/eiu-app-launcher-menu.token.mjs +6 -0
  5. package/esm2020/lib/app-launcher/eiu-app-launcher.component.mjs +89 -0
  6. package/esm2020/lib/app-launcher/eiu-app-launcher.module.mjs +22 -0
  7. package/esm2020/lib/eiu-app-kit.tokens.mjs +7 -0
  8. package/esm2020/lib/feature-news/constants.mjs +8 -0
  9. package/esm2020/lib/feature-news/eiu-app-feature-news.module.mjs +22 -0
  10. package/esm2020/lib/feature-news/feature-news-dialog.component.mjs +41 -0
  11. package/esm2020/lib/feature-news/feature-news-dialog.service.mjs +30 -0
  12. package/esm2020/lib/feature-news/feature-news.service.mjs +55 -0
  13. package/esm2020/lib/feature-news/models/operation-result.model.mjs +2 -0
  14. package/esm2020/lib/feature-news/models/redmine-news.model.mjs +2 -0
  15. package/esm2020/lib/feature-news/news-api.service.mjs +36 -0
  16. package/esm2020/lib/footer/eiu-app-footer.component.mjs +38 -0
  17. package/esm2020/lib/footer/eiu-app-footer.module.mjs +18 -0
  18. package/esm2020/lib/sidebar-shared/contact/eiu-contact-support.component.mjs +23 -0
  19. package/esm2020/lib/sidebar-shared/eiu-sidebar-shared.module.mjs +20 -0
  20. package/esm2020/lib/sidebar-shared/logo/eiu-sidebar-logo.component.mjs +26 -0
  21. package/esm2020/lib/sidebar-shared/sidebar-shared.model.mjs +2 -0
  22. package/esm2020/public-api.mjs +23 -0
  23. package/fesm2015/eiu-app-kit.mjs +418 -0
  24. package/fesm2015/eiu-app-kit.mjs.map +1 -0
  25. package/fesm2020/eiu-app-kit.mjs +409 -0
  26. package/fesm2020/eiu-app-kit.mjs.map +1 -0
  27. package/index.d.ts +5 -0
  28. package/lib/app-launcher/eiu-app-launcher-menu.model.d.ts +10 -0
  29. package/lib/app-launcher/eiu-app-launcher-menu.token.d.ts +10 -0
  30. package/lib/app-launcher/eiu-app-launcher.component.d.ts +30 -0
  31. package/lib/app-launcher/eiu-app-launcher.module.d.ts +12 -0
  32. package/lib/eiu-app-kit.tokens.d.ts +6 -0
  33. package/lib/feature-news/constants.d.ts +7 -0
  34. package/lib/feature-news/eiu-app-feature-news.module.d.ts +12 -0
  35. package/lib/feature-news/feature-news-dialog.component.d.ts +16 -0
  36. package/lib/feature-news/feature-news-dialog.service.d.ts +15 -0
  37. package/lib/feature-news/feature-news.service.d.ts +13 -0
  38. package/lib/feature-news/models/operation-result.model.d.ts +6 -0
  39. package/lib/feature-news/models/redmine-news.model.d.ts +17 -0
  40. package/lib/feature-news/news-api.service.d.ts +11 -0
  41. package/lib/footer/eiu-app-footer.component.d.ts +14 -0
  42. package/lib/footer/eiu-app-footer.module.d.ts +8 -0
  43. package/lib/sidebar-shared/contact/eiu-contact-support.component.d.ts +9 -0
  44. package/lib/sidebar-shared/eiu-sidebar-shared.module.d.ts +10 -0
  45. package/lib/sidebar-shared/logo/eiu-sidebar-logo.component.d.ts +9 -0
  46. package/lib/sidebar-shared/sidebar-shared.model.d.ts +12 -0
  47. package/package.json +54 -0
  48. package/public-api.d.ts +19 -0
@@ -0,0 +1,409 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, EventEmitter, Component, Inject, Output, Input, HostListener, NgModule, inject, Injectable } from '@angular/core';
3
+ import * as i1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i1$1 from '@angular/router';
6
+ import { RouterModule } from '@angular/router';
7
+ import { HttpClient, HttpParams } from '@angular/common/http';
8
+ import { catchError, of, BehaviorSubject, Subject, take, switchMap, tap, map } from 'rxjs';
9
+
10
+ /**
11
+ * Base URL API EIU App (ví dụ `environment.API_EIU_APP`).
12
+ * Dùng cho News API, icon launcher, … — cung cấp một lần trong `AppModule`.
13
+ */
14
+ const EIU_APP_API_BASE_URL = new InjectionToken('EIU_APP_API_BASE_URL');
15
+
16
+ /**
17
+ * Host app cung cấp cách load danh sách ứng dụng (ví dụ `ProjectService.getAllProject()`).
18
+ */
19
+ const EIU_APP_LAUNCHER_MENU_LOADER = new InjectionToken('EIU_APP_LAUNCHER_MENU_LOADER');
20
+
21
+ class EiuAppLauncherComponent {
22
+ constructor(_menuLoader, apiBaseUrl, _cdr) {
23
+ this._menuLoader = _menuLoader;
24
+ this._cdr = _cdr;
25
+ /** Phát ra khi user bấm icon menu (sidebar). Host gắn với store / layout. */
26
+ this.sidebarToggle = new EventEmitter();
27
+ this.appLabel = 'Ứng dụng';
28
+ this.isScreenWide = true;
29
+ this.menuItems = [];
30
+ this._apiBase = (apiBaseUrl || '').replace(/\/$/, '');
31
+ }
32
+ ngOnInit() {
33
+ this._loadMenu();
34
+ }
35
+ onResize() {
36
+ this._adjustMenuPosition();
37
+ }
38
+ ngAfterViewInit() {
39
+ this._adjustMenuPosition();
40
+ this._cdr.detectChanges();
41
+ }
42
+ onToggleMenuSidebar() {
43
+ this.sidebarToggle.emit();
44
+ }
45
+ _adjustMenuPosition() {
46
+ this.isScreenWide = window.innerWidth > 1300;
47
+ }
48
+ _loadMenu() {
49
+ this._menuLoader.loadItems().subscribe({
50
+ next: (res) => {
51
+ this.menuItems = (res ?? [])
52
+ .filter((x) => x.link)
53
+ .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
54
+ .map((x) => ({
55
+ label: x.name_VI ?? '',
56
+ icon: this._iconUrl(x.imageUrl),
57
+ link: x.link,
58
+ isActive: this._isActive(x.link)
59
+ }));
60
+ },
61
+ error: (err) => console.error(err)
62
+ });
63
+ }
64
+ _iconUrl(imageUrl) {
65
+ if (!imageUrl) {
66
+ return '';
67
+ }
68
+ if (/^https?:\/\//i.test(imageUrl)) {
69
+ return imageUrl;
70
+ }
71
+ const path = imageUrl.replace(/^\//, '');
72
+ return this._apiBase ? `${this._apiBase}/${path}` : path;
73
+ }
74
+ _isActive(link) {
75
+ try {
76
+ const currentDomain = new URL(window.location.href).origin;
77
+ const linkDomain = new URL(link).origin;
78
+ return currentDomain === linkDomain;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ }
85
+ EiuAppLauncherComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppLauncherComponent, deps: [{ token: EIU_APP_LAUNCHER_MENU_LOADER }, { token: EIU_APP_API_BASE_URL }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
86
+ EiuAppLauncherComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: EiuAppLauncherComponent, selector: "app-eiu-app-launcher", inputs: { appLabel: "appLabel" }, outputs: { sidebarToggle: "sidebarToggle" }, host: { listeners: { "window:resize": "onResize()" } }, ngImport: i0, template: "<ul class=\"navbar-nav\">\n <li class=\"nav-item\">\n <a class=\"nav-link\" href=\"#\" (click)=\"onToggleMenuSidebar(); $event.preventDefault()\" role=\"button\"><i\n class=\"fas fa-bars\"></i></a>\n </li>\n <ng-container *ngIf=\"isScreenWide\">\n <li class=\"nav-item\" *ngFor=\"let menuItem of menuItems | slice: 0:5\">\n <a class=\"nav-link\" href=\"{{ menuItem.link }}\" [ngClass]=\"{ active: menuItem.isActive }\" target=\"_blank\">\n {{ menuItem.label }}\n </a>\n </li>\n </ng-container>\n <li class=\"nav-item dropdown\">\n <a class=\"nav-link dropdown-toggle\" role=\"button\" aria-expanded=\"false\">\n {{ appLabel }}\n </a>\n\n <!-- Mega Menu -->\n <div class=\"mega-dropdown\" id=\"megaDropdown\">\n <div class=\"menu-item-card\" *ngFor=\"let menuItem of menuItems\">\n <a class=\"dropdown-item\" href=\"{{ menuItem.link }}\" [ngClass]=\"{ active: menuItem.isActive }\"\n target=\"_blank\">\n <div class=\"icon-container\">\n <img [src]=\"menuItem.icon\" alt=\"\" height=\"25\" width=\"25\" />\n </div>\n <span>{{ menuItem.label }}</span>\n </a>\n </div>\n </div>\n </li>\n</ul>", styles: ["img{object-fit:scale-down}.fa-bars,.nav-item .nav-link{cursor:pointer}.nav-item button.nav-link{outline:none;border:none;background-color:transparent}.mega-dropdown{display:none;opacity:0;transform:translateY(10px);transition:all .2s ease-in-out;position:absolute;top:100%;z-index:9999;background-color:#fff;padding:10px;border-radius:8px;box-shadow:0 2px 8px #00000014;width:600px;grid-template-columns:repeat(3,1fr);gap:8px}.nav-item.dropdown:hover>.mega-dropdown{display:grid;opacity:1;z-index:9999;transform:translateY(0)}.menu-item-card{padding:5px;transition:transform .2s ease}.dropdown-item{display:flex;align-items:center;padding:8px 12px;border-radius:6px;box-shadow:none;transition:background-color .2s ease,transform .2s ease;color:#194266}.dropdown-item:hover,.dropdown-item.active{background-color:#f0f5fd;color:var(--color-eiu-main)}.icon-container{width:28px;height:28px;border:1px solid #194266;border-radius:6px;display:flex;justify-content:center;align-items:center;margin-right:10px}.icon-container i{font-size:14px;color:#fff}.navbar-nav .nav-item{display:inline-block}.navbar-nav .nav-link{display:flex;align-items:center;border-radius:6px;transition:background-color .2s ease;font-size:16px;color:var(--color-eiu-primary);padding:6px 12px}.navbar-nav .nav-link:hover{color:var(--color-eiu-main)}.navbar-nav .nav-link.active{color:var(--color-eiu-main);font-weight:600}@media (max-width: 768px){.mega-dropdown{width:auto;right:auto;height:380px;overflow:scroll;left:50%;transform:translate(-50%);grid-template-columns:1fr}::ng-deep .navbar-expand .navbar-nav .nav-link{padding-left:0rem!important}}@media (min-width: 769px) and (max-width: 1480px){.mega-dropdown{width:500px;grid-template-columns:repeat(2,1fr)}}@media (min-width: 1481px){.mega-dropdown{width:auto;grid-template-columns:repeat(3,1fr)}}\n"], dependencies: [{ kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.SlicePipe, name: "slice" }] });
87
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppLauncherComponent, decorators: [{
88
+ type: Component,
89
+ args: [{ selector: 'app-eiu-app-launcher', template: "<ul class=\"navbar-nav\">\n <li class=\"nav-item\">\n <a class=\"nav-link\" href=\"#\" (click)=\"onToggleMenuSidebar(); $event.preventDefault()\" role=\"button\"><i\n class=\"fas fa-bars\"></i></a>\n </li>\n <ng-container *ngIf=\"isScreenWide\">\n <li class=\"nav-item\" *ngFor=\"let menuItem of menuItems | slice: 0:5\">\n <a class=\"nav-link\" href=\"{{ menuItem.link }}\" [ngClass]=\"{ active: menuItem.isActive }\" target=\"_blank\">\n {{ menuItem.label }}\n </a>\n </li>\n </ng-container>\n <li class=\"nav-item dropdown\">\n <a class=\"nav-link dropdown-toggle\" role=\"button\" aria-expanded=\"false\">\n {{ appLabel }}\n </a>\n\n <!-- Mega Menu -->\n <div class=\"mega-dropdown\" id=\"megaDropdown\">\n <div class=\"menu-item-card\" *ngFor=\"let menuItem of menuItems\">\n <a class=\"dropdown-item\" href=\"{{ menuItem.link }}\" [ngClass]=\"{ active: menuItem.isActive }\"\n target=\"_blank\">\n <div class=\"icon-container\">\n <img [src]=\"menuItem.icon\" alt=\"\" height=\"25\" width=\"25\" />\n </div>\n <span>{{ menuItem.label }}</span>\n </a>\n </div>\n </div>\n </li>\n</ul>", styles: ["img{object-fit:scale-down}.fa-bars,.nav-item .nav-link{cursor:pointer}.nav-item button.nav-link{outline:none;border:none;background-color:transparent}.mega-dropdown{display:none;opacity:0;transform:translateY(10px);transition:all .2s ease-in-out;position:absolute;top:100%;z-index:9999;background-color:#fff;padding:10px;border-radius:8px;box-shadow:0 2px 8px #00000014;width:600px;grid-template-columns:repeat(3,1fr);gap:8px}.nav-item.dropdown:hover>.mega-dropdown{display:grid;opacity:1;z-index:9999;transform:translateY(0)}.menu-item-card{padding:5px;transition:transform .2s ease}.dropdown-item{display:flex;align-items:center;padding:8px 12px;border-radius:6px;box-shadow:none;transition:background-color .2s ease,transform .2s ease;color:#194266}.dropdown-item:hover,.dropdown-item.active{background-color:#f0f5fd;color:var(--color-eiu-main)}.icon-container{width:28px;height:28px;border:1px solid #194266;border-radius:6px;display:flex;justify-content:center;align-items:center;margin-right:10px}.icon-container i{font-size:14px;color:#fff}.navbar-nav .nav-item{display:inline-block}.navbar-nav .nav-link{display:flex;align-items:center;border-radius:6px;transition:background-color .2s ease;font-size:16px;color:var(--color-eiu-primary);padding:6px 12px}.navbar-nav .nav-link:hover{color:var(--color-eiu-main)}.navbar-nav .nav-link.active{color:var(--color-eiu-main);font-weight:600}@media (max-width: 768px){.mega-dropdown{width:auto;right:auto;height:380px;overflow:scroll;left:50%;transform:translate(-50%);grid-template-columns:1fr}::ng-deep .navbar-expand .navbar-nav .nav-link{padding-left:0rem!important}}@media (min-width: 769px) and (max-width: 1480px){.mega-dropdown{width:500px;grid-template-columns:repeat(2,1fr)}}@media (min-width: 1481px){.mega-dropdown{width:auto;grid-template-columns:repeat(3,1fr)}}\n"] }]
90
+ }], ctorParameters: function () { return [{ type: undefined, decorators: [{
91
+ type: Inject,
92
+ args: [EIU_APP_LAUNCHER_MENU_LOADER]
93
+ }] }, { type: undefined, decorators: [{
94
+ type: Inject,
95
+ args: [EIU_APP_API_BASE_URL]
96
+ }] }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { sidebarToggle: [{
97
+ type: Output
98
+ }], appLabel: [{
99
+ type: Input
100
+ }], onResize: [{
101
+ type: HostListener,
102
+ args: ['window:resize']
103
+ }] } });
104
+
105
+ /**
106
+ * Import ở shell layout (ví dụ `ThemeModule`).
107
+ * Cần `EIU_APP_API_BASE_URL` và `EIU_APP_LAUNCHER_MENU_LOADER` trong `providers`.
108
+ */
109
+ class EiuAppLauncherModule {
110
+ }
111
+ EiuAppLauncherModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppLauncherModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
112
+ EiuAppLauncherModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: EiuAppLauncherModule, declarations: [EiuAppLauncherComponent], imports: [CommonModule], exports: [EiuAppLauncherComponent] });
113
+ EiuAppLauncherModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppLauncherModule, imports: [CommonModule] });
114
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppLauncherModule, decorators: [{
115
+ type: NgModule,
116
+ args: [{
117
+ imports: [CommonModule],
118
+ declarations: [EiuAppLauncherComponent],
119
+ exports: [EiuAppLauncherComponent]
120
+ }]
121
+ }] });
122
+
123
+ class EiuSidebarLogoComponent {
124
+ constructor() {
125
+ this.imageUrl = '';
126
+ this.alt = 'Đại học Quốc tế Miền Đông (EIU)';
127
+ this.text = 'Đại học Quốc tế Miền Đông (EIU)';
128
+ this.homeLink = '/';
129
+ }
130
+ }
131
+ EiuSidebarLogoComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuSidebarLogoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
132
+ EiuSidebarLogoComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: EiuSidebarLogoComponent, selector: "app-eiu-sidebar-logo", inputs: { imageUrl: "imageUrl", alt: "alt", text: "text", homeLink: "homeLink" }, ngImport: i0, template: "<a [routerLink]=\"[homeLink]\" class=\"brand-link\">\r\n <img\r\n class=\"brand-image\"\r\n [src]=\"imageUrl\"\r\n [alt]=\"alt\"\r\n height=\"40\"\r\n />\r\n <strong class=\"brand-text font-weight-light\">{{ text }}</strong>\r\n</a>\r\n", styles: [".brand-image{float:left;line-height:.8;margin:-1px 8px 0 6px;opacity:.8}\n"], dependencies: [{ kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
133
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuSidebarLogoComponent, decorators: [{
134
+ type: Component,
135
+ args: [{ selector: 'app-eiu-sidebar-logo', template: "<a [routerLink]=\"[homeLink]\" class=\"brand-link\">\r\n <img\r\n class=\"brand-image\"\r\n [src]=\"imageUrl\"\r\n [alt]=\"alt\"\r\n height=\"40\"\r\n />\r\n <strong class=\"brand-text font-weight-light\">{{ text }}</strong>\r\n</a>\r\n", styles: [".brand-image{float:left;line-height:.8;margin:-1px 8px 0 6px;opacity:.8}\n"] }]
136
+ }], propDecorators: { imageUrl: [{
137
+ type: Input
138
+ }], alt: [{
139
+ type: Input
140
+ }], text: [{
141
+ type: Input
142
+ }], homeLink: [{
143
+ type: Input
144
+ }] } });
145
+
146
+ class EiuContactSupportComponent {
147
+ constructor() {
148
+ this.title = 'Liên hệ';
149
+ this.contacts = [];
150
+ }
151
+ get sortedContacts() {
152
+ return [...(this.contacts || [])].sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0));
153
+ }
154
+ }
155
+ EiuContactSupportComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuContactSupportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
156
+ EiuContactSupportComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: EiuContactSupportComponent, selector: "app-eiu-contact-support", inputs: { title: "title", contacts: "contacts" }, ngImport: i0, template: "<div class=\"box-contact\">\n <div class=\"header-contact\"></div>\n <li class=\"nav-header\">\n <strong class=\"contact-title\">\n <i class=\"nav-icon fa fa-headphones\"></i>&nbsp;&nbsp;{{ title }}\n </strong>\n </li>\n <li class=\"nav-item\" *ngFor=\"let item of sortedContacts\">\n <div class=\"nav-link contact-content\">\n <p class=\"text contact-name\">{{ item?.fullName }}</p>\n <div class=\"contact-line\">\n <a class=\"text-sm contact-link\" [href]=\"'tel:' + item?.phoneNumber\" [title]=\"item?.phoneNumber\">\n <span class=\"contact-meta\">M: {{ item?.phoneNumber }}</span>\n </a>\n </div>\n <div class=\"contact-line\">\n <a class=\"text-sm contact-link\" [href]=\"'mailto:' + item?.email\" [title]=\"item?.email\">\n <span class=\"contact-meta\">E: {{ item?.email }}</span>\n </a>\n </div>\n </div>\n </li>\n</div>", styles: [".header-contact{margin:40px 0 5px;border-top:1px #3d81c3 solid}.nav-sidebar .nav-header{font-size:.9rem;padding:.5rem .75rem}.nav-header{background-color:inherit;color:#d0d4db}.contact-title{font-size:20px}.contact-content{padding:10px 30px}.contact-name{color:#fff}.contact-meta{color:#aaabae}.contact-link{text-decoration:none}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuContactSupportComponent, decorators: [{
158
+ type: Component,
159
+ args: [{ selector: 'app-eiu-contact-support', template: "<div class=\"box-contact\">\n <div class=\"header-contact\"></div>\n <li class=\"nav-header\">\n <strong class=\"contact-title\">\n <i class=\"nav-icon fa fa-headphones\"></i>&nbsp;&nbsp;{{ title }}\n </strong>\n </li>\n <li class=\"nav-item\" *ngFor=\"let item of sortedContacts\">\n <div class=\"nav-link contact-content\">\n <p class=\"text contact-name\">{{ item?.fullName }}</p>\n <div class=\"contact-line\">\n <a class=\"text-sm contact-link\" [href]=\"'tel:' + item?.phoneNumber\" [title]=\"item?.phoneNumber\">\n <span class=\"contact-meta\">M: {{ item?.phoneNumber }}</span>\n </a>\n </div>\n <div class=\"contact-line\">\n <a class=\"text-sm contact-link\" [href]=\"'mailto:' + item?.email\" [title]=\"item?.email\">\n <span class=\"contact-meta\">E: {{ item?.email }}</span>\n </a>\n </div>\n </div>\n </li>\n</div>", styles: [".header-contact{margin:40px 0 5px;border-top:1px #3d81c3 solid}.nav-sidebar .nav-header{font-size:.9rem;padding:.5rem .75rem}.nav-header{background-color:inherit;color:#d0d4db}.contact-title{font-size:20px}.contact-content{padding:10px 30px}.contact-name{color:#fff}.contact-meta{color:#aaabae}.contact-link{text-decoration:none}\n"] }]
160
+ }], propDecorators: { title: [{
161
+ type: Input
162
+ }], contacts: [{
163
+ type: Input
164
+ }] } });
165
+
166
+ class EiuSidebarSharedModule {
167
+ }
168
+ EiuSidebarSharedModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuSidebarSharedModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
169
+ EiuSidebarSharedModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: EiuSidebarSharedModule, declarations: [EiuSidebarLogoComponent, EiuContactSupportComponent], imports: [CommonModule, RouterModule], exports: [EiuSidebarLogoComponent, EiuContactSupportComponent] });
170
+ EiuSidebarSharedModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuSidebarSharedModule, imports: [CommonModule, RouterModule] });
171
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuSidebarSharedModule, decorators: [{
172
+ type: NgModule,
173
+ args: [{
174
+ imports: [CommonModule, RouterModule],
175
+ declarations: [EiuSidebarLogoComponent, EiuContactSupportComponent],
176
+ exports: [EiuSidebarLogoComponent, EiuContactSupportComponent]
177
+ }]
178
+ }] });
179
+
180
+ class EiuAppFooterComponent {
181
+ constructor() {
182
+ this.appVersion = '';
183
+ this.copyrightYear = '2023';
184
+ this.companyName = 'IT - EIU';
185
+ this.companyUrl = 'https://sites.google.com/eiu.edu.vn/oit/';
186
+ this.allRightsText = 'All rights reserved.';
187
+ this.ringLabel = 'Phiên bản';
188
+ this.ringClick = new EventEmitter();
189
+ }
190
+ onRingClick(event) {
191
+ event.preventDefault();
192
+ this.ringClick.emit();
193
+ }
194
+ }
195
+ EiuAppFooterComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFooterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
196
+ EiuAppFooterComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: EiuAppFooterComponent, selector: "app-eiu-app-footer", inputs: { appVersion: "appVersion", copyrightYear: "copyrightYear", companyName: "companyName", companyUrl: "companyUrl", allRightsText: "allRightsText", ringLabel: "ringLabel" }, outputs: { ringClick: "ringClick" }, ngImport: i0, template: "<div class=\"float-right d-none d-sm-block footer-version-wrap\">\r\n <a href=\"#\" class=\"radar-ring\" (click)=\"onRingClick($event)\">\r\n <span>{{ ringLabel }}</span>\r\n </a>\r\n <b>Version</b> {{ appVersion }}\r\n</div>\r\n<strong>\r\n <span>Copyright &copy; {{ copyrightYear }}</span>\r\n <a [href]=\"companyUrl\" target=\"_blank\" rel=\"noopener\" style=\"margin: 0\">\r\n {{ companyName }}</a\r\n >\r\n <span>.</span>\r\n</strong>\r\n<span> {{ allRightsText }}</span>\r\n", styles: [".footer-version-wrap{display:flex;align-items:center;gap:10px}.radar-ring{position:relative;width:34px;height:34px;border-radius:50%;background:#194266;color:#fff;font-size:8px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;text-decoration:none}.radar-ring:before,.radar-ring:after{content:\"\";position:absolute;inset:-6px;border-radius:50%;border:2px solid rgba(25,66,102,.35);animation:radarPulse 2.2s infinite}.radar-ring:after{animation-delay:1.1s}@keyframes radarPulse{0%{transform:scale(.8);opacity:.75}to{transform:scale(1.45);opacity:0}}\n"] });
197
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFooterComponent, decorators: [{
198
+ type: Component,
199
+ args: [{ selector: 'app-eiu-app-footer', template: "<div class=\"float-right d-none d-sm-block footer-version-wrap\">\r\n <a href=\"#\" class=\"radar-ring\" (click)=\"onRingClick($event)\">\r\n <span>{{ ringLabel }}</span>\r\n </a>\r\n <b>Version</b> {{ appVersion }}\r\n</div>\r\n<strong>\r\n <span>Copyright &copy; {{ copyrightYear }}</span>\r\n <a [href]=\"companyUrl\" target=\"_blank\" rel=\"noopener\" style=\"margin: 0\">\r\n {{ companyName }}</a\r\n >\r\n <span>.</span>\r\n</strong>\r\n<span> {{ allRightsText }}</span>\r\n", styles: [".footer-version-wrap{display:flex;align-items:center;gap:10px}.radar-ring{position:relative;width:34px;height:34px;border-radius:50%;background:#194266;color:#fff;font-size:8px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;text-decoration:none}.radar-ring:before,.radar-ring:after{content:\"\";position:absolute;inset:-6px;border-radius:50%;border:2px solid rgba(25,66,102,.35);animation:radarPulse 2.2s infinite}.radar-ring:after{animation-delay:1.1s}@keyframes radarPulse{0%{transform:scale(.8);opacity:.75}to{transform:scale(1.45);opacity:0}}\n"] }]
200
+ }], propDecorators: { appVersion: [{
201
+ type: Input
202
+ }], copyrightYear: [{
203
+ type: Input
204
+ }], companyName: [{
205
+ type: Input
206
+ }], companyUrl: [{
207
+ type: Input
208
+ }], allRightsText: [{
209
+ type: Input
210
+ }], ringLabel: [{
211
+ type: Input
212
+ }], ringClick: [{
213
+ type: Output
214
+ }] } });
215
+
216
+ class EiuAppFooterModule {
217
+ }
218
+ EiuAppFooterModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFooterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
219
+ EiuAppFooterModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFooterModule, declarations: [EiuAppFooterComponent], imports: [CommonModule], exports: [EiuAppFooterComponent] });
220
+ EiuAppFooterModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFooterModule, imports: [CommonModule] });
221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFooterModule, decorators: [{
222
+ type: NgModule,
223
+ args: [{
224
+ imports: [CommonModule],
225
+ declarations: [EiuAppFooterComponent],
226
+ exports: [EiuAppFooterComponent]
227
+ }]
228
+ }] });
229
+
230
+ /** Prefix localStorage: feature_news_seen::<userId>::<projectIdentifier> */
231
+ const FEATURE_NEWS_SEEN_STORAGE_KEY_PREFIX = 'feature_news_seen::';
232
+ /**
233
+ * Mặc định project identifier trên Redmine (EIU App).
234
+ * Ứng dụng host có thể dùng hằng này hoặc chuỗi riêng khi gọi service.
235
+ */
236
+ const FEATURE_NEWS_REDMINE_PROJECT_IDENTIFIER = 'login-sso-quan-ly-nhan-su';
237
+
238
+ class NewsApiService {
239
+ constructor() {
240
+ this._httpClient = inject(HttpClient);
241
+ this._apiBaseUrl = inject(EIU_APP_API_BASE_URL);
242
+ }
243
+ getNewsByProjectIdentifier(projectIdentifier, limit = 1) {
244
+ const params = new HttpParams()
245
+ .set('projectIdentifier', projectIdentifier)
246
+ .set('limit', String(limit));
247
+ const url = `${this._apiBaseUrl}/News/GetRedmineNewsByProjectIdentifier`;
248
+ return this._httpClient
249
+ .get(url, { params })
250
+ .pipe(catchError((error) => of({
251
+ success: false,
252
+ statusCode: 500,
253
+ message: error instanceof Error
254
+ ? error.message
255
+ : 'Cannot get feature news from API.',
256
+ data: null
257
+ })));
258
+ }
259
+ }
260
+ NewsApiService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NewsApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
261
+ NewsApiService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NewsApiService, providedIn: 'root' });
262
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: NewsApiService, decorators: [{
263
+ type: Injectable,
264
+ args: [{
265
+ providedIn: 'root'
266
+ }]
267
+ }] });
268
+
269
+ class FeatureNewsDialogService {
270
+ constructor() {
271
+ this._dialogState = new BehaviorSubject(null);
272
+ this._closeNotifier = null;
273
+ this.dialogState$ = this._dialogState.asObservable();
274
+ }
275
+ open(news) {
276
+ this._closeNotifier = new Subject();
277
+ this._dialogState.next({ news });
278
+ return this._closeNotifier.asObservable().pipe(take(1));
279
+ }
280
+ close() {
281
+ this._dialogState.next(null);
282
+ this._closeNotifier?.next();
283
+ this._closeNotifier?.complete();
284
+ this._closeNotifier = null;
285
+ }
286
+ }
287
+ FeatureNewsDialogService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: FeatureNewsDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
288
+ FeatureNewsDialogService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: FeatureNewsDialogService, providedIn: 'root' });
289
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: FeatureNewsDialogService, decorators: [{
290
+ type: Injectable,
291
+ args: [{
292
+ providedIn: 'root'
293
+ }]
294
+ }] });
295
+
296
+ class FeatureNewsService {
297
+ constructor() {
298
+ this._newsApiService = inject(NewsApiService);
299
+ this._featureNewsDialogService = inject(FeatureNewsDialogService);
300
+ }
301
+ buildSeenKey(userId, projectIdentifier) {
302
+ return `${FEATURE_NEWS_SEEN_STORAGE_KEY_PREFIX}${userId}::${projectIdentifier}`;
303
+ }
304
+ hasSeenLatestNews(userId, projectIdentifier, latestNewsId) {
305
+ const key = this.buildSeenKey(userId, projectIdentifier);
306
+ return localStorage.getItem(key) === String(latestNewsId);
307
+ }
308
+ markSeen(userId, projectIdentifier, latestNewsId) {
309
+ const key = this.buildSeenKey(userId, projectIdentifier);
310
+ localStorage.setItem(key, String(latestNewsId));
311
+ }
312
+ checkAndShowFeatureNews(userId, projectIdentifier) {
313
+ return this._newsApiService.getNewsByProjectIdentifier(projectIdentifier, 1).pipe(switchMap((result) => {
314
+ const news = result.success ? (result.data?.news ?? []).slice(0, 1) : [];
315
+ if (!news.length) {
316
+ return of(void 0);
317
+ }
318
+ const latestNews = news[0];
319
+ if (this.hasSeenLatestNews(userId, projectIdentifier, latestNews.id)) {
320
+ return of(void 0);
321
+ }
322
+ return this._featureNewsDialogService.open(news).pipe(tap(() => this.markSeen(userId, projectIdentifier, latestNews.id)), map(() => void 0));
323
+ }), catchError(() => of(void 0)));
324
+ }
325
+ showLatestFeatureNewsManually(userId, projectIdentifier) {
326
+ return this._newsApiService.getNewsByProjectIdentifier(projectIdentifier, 1).pipe(switchMap((result) => {
327
+ const news = result.success ? (result.data?.news ?? []).slice(0, 1) : [];
328
+ if (!news.length) {
329
+ return of(void 0);
330
+ }
331
+ const latestNews = news[0];
332
+ return this._featureNewsDialogService.open(news).pipe(tap(() => this.markSeen(userId, projectIdentifier, latestNews.id)), map(() => void 0));
333
+ }), catchError(() => of(void 0)));
334
+ }
335
+ }
336
+ FeatureNewsService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: FeatureNewsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
337
+ FeatureNewsService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: FeatureNewsService, providedIn: 'root' });
338
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: FeatureNewsService, decorators: [{
339
+ type: Injectable,
340
+ args: [{
341
+ providedIn: 'root'
342
+ }]
343
+ }] });
344
+
345
+ class FeatureNewsDialogComponent {
346
+ constructor() {
347
+ this._featureNewsDialogService = inject(FeatureNewsDialogService);
348
+ this._subscription = null;
349
+ this.dialogState = null;
350
+ }
351
+ ngOnInit() {
352
+ this._subscription = this._featureNewsDialogService.dialogState$.subscribe((state) => {
353
+ this.dialogState = state;
354
+ });
355
+ }
356
+ ngOnDestroy() {
357
+ this._subscription?.unsubscribe();
358
+ }
359
+ getSummary(item) {
360
+ return this._truncateText(item.summary || '', 200);
361
+ }
362
+ closeDialog() {
363
+ this._featureNewsDialogService.close();
364
+ }
365
+ _truncateText(text, maxLength) {
366
+ if (!text) {
367
+ return '';
368
+ }
369
+ if (text.length <= maxLength) {
370
+ return text;
371
+ }
372
+ return `${text.slice(0, maxLength)}...`;
373
+ }
374
+ }
375
+ FeatureNewsDialogComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: FeatureNewsDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
376
+ FeatureNewsDialogComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: FeatureNewsDialogComponent, selector: "app-feature-news-dialog", ngImport: i0, template: "<div class=\"feature-news-overlay\" *ngIf=\"dialogState\">\n <div class=\"feature-news-dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"feature-news-title\">\n <div class=\"feature-news-header\">\n <div class=\"feature-news-header__main\">\n <div class=\"feature-news-brand\">\n <div class=\"feature-news-title-wrap mt-2\">\n <span id=\"feature-news-title\" class=\"feature-news-chip\">C\u1EADp nh\u1EADt t\u00EDnh n\u0103ng</span>\n </div>\n </div>\n </div>\n <button type=\"button\" class=\"feature-news-close\" aria-label=\"Dong\" (click)=\"closeDialog()\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n\n <div class=\"feature-news-body\">\n <div class=\"feature-news-item\" *ngFor=\"let item of dialogState?.news\">\n <div class=\"feature-news-item-head\">\n <h5 class=\"feature-news-content-label mt-2\">{{ item?.title }}</h5>\n <span class=\"feature-news-item-tag\">M\u1EDBi nh\u1EA5t</span>\n </div>\n <div class=\"feature-news-content-block\" *ngIf=\"item?.summary\">\n <div class=\"feature-news-content-label mt-2\">T\u00F3m t\u1EAFt</div>\n <div class=\"feature-news-content-panel\">\n <p class=\"feature-news-item-summary\">{{ getSummary(item) }}</p>\n </div>\n </div>\n <div class=\"feature-news-content-block\" *ngIf=\"item?.description\">\n <div class=\"feature-news-content-label mt-2\">Chi ti\u1EBFt</div>\n <div class=\"feature-news-content-panel feature-news-content-panel--detail\">\n <p class=\"feature-news-item-description\">{{ item?.description }}</p>\n </div>\n </div>\n <div class=\"feature-news-item-meta\">\n <span class=\"feature-news-item-meta__dot\" *ngIf=\"item?.author?.name\" aria-hidden=\"true\"></span>\n <span class=\"feature-news-item-meta__date\">{{\n item?.createdOn | date : 'dd/MM/yyyy'\n }}</span>\n\n </div>\n </div>\n </div>\n\n <div class=\"feature-news-footer\">\n <div class=\"feature-news-footer-note\">\n <span class=\"feature-news-footer-note__icon\" aria-hidden=\"true\"></span>\n Th\u00F4ng b\u00E1o ch\u1EC9 hi\u1EC7n khi c\u00F3 tin m\u1EDBi; b\u1EA1n lu\u00F4n c\u00F3 th\u1EC3 xem l\u1EA1i t\u1EEB footer.\n </div>\n <button type=\"button\" class=\"feature-news-confirm-btn\" (click)=\"closeDialog()\">\n \u0110\u00E3 hi\u1EC3u\n </button>\n </div>\n </div>\n</div>", styles: ["@charset \"UTF-8\";.feature-news-overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:2000;padding:24px 16px;background:radial-gradient(ellipse 120% 80% at 50% 0%,rgba(30,58,95,.45) 0%,transparent 55%),rgba(15,23,42,.62);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}.feature-news-dialog{--fn-gold: #c9a227;--fn-gold-soft: rgba(201, 162, 39, .35);--fn-navy: #0f172a;--fn-navy-mid: #1e293b;position:relative;width:100%;max-width:720px;max-height:min(86vh,880px);display:flex;flex-direction:column;border-radius:16px;overflow:hidden;background:linear-gradient(180deg,#ffffff 0%,#f8fafc 100%);border:1px solid rgba(255,255,255,.65);box-shadow:0 0 0 1px #0f172a0f,0 24px 56px #0f172a38,0 2px #ffffffe6 inset}.feature-news-header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;padding:20px 22px 16px;background:linear-gradient(165deg,#f8fafc 0%,#ffffff 42%,rgba(248,250,252,.96) 100%);border-bottom:1px solid rgba(148,163,184,.28)}.feature-news-header__main{flex:1;min-width:0}.feature-news-brand{display:flex;gap:14px;align-items:flex-start}.feature-news-title-wrap{display:flex;flex-direction:column;gap:8px;min-width:0}.feature-news-chip{font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#1d4ed8}.feature-news-title-wrap h4{margin:0;color:var(--fn-navy);font-size:21px;font-weight:800;letter-spacing:-.02em;line-height:1.25}.feature-news-subtitle{margin:0;color:#64748b;font-size:13px;line-height:1.5;max-width:52ch}.feature-news-close{flex-shrink:0;width:38px;height:38px;border:1px solid rgba(148,163,184,.35);border-radius:12px;background:linear-gradient(180deg,#ffffff 0%,#f1f5f9 100%);color:#64748b;font-size:22px;line-height:1;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:color .2s ease,border-color .2s ease,box-shadow .2s ease}.feature-news-close:hover{color:var(--fn-navy);border-color:#2563eb59;box-shadow:0 4px 14px #2563eb1f}.feature-news-close:focus{outline:none;box-shadow:0 0 0 3px #93c5fd8c}.feature-news-body{flex:1 1 auto;overflow-y:auto;padding:18px 22px 14px;background:linear-gradient(180deg,#f8fafc 0%,#ffffff 100%);-webkit-overflow-scrolling:touch}.feature-news-item{border-radius:14px;padding:0;margin-bottom:0;background:#ffffff;border:1px solid rgba(148,163,184,.22);box-shadow:0 1px #ffffffe6 inset,0 8px 28px #0f172a0f;overflow:hidden}.feature-news-item-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px 18px 12px;background:linear-gradient(180deg,rgba(248,250,252,.9) 0%,#ffffff 100%);border-bottom:1px solid rgba(226,232,240,.9)}.feature-news-item-title{margin:0;font-size:18px;font-weight:800;color:var(--fn-navy);letter-spacing:-.02em;line-height:1.3}.feature-news-item-tag{flex-shrink:0;padding:5px 11px;border-radius:999px;font-size:10px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#1e40af;background:rgba(239,246,255,.95);border:1px solid rgba(191,219,254,.95)}.feature-news-content-block{padding:0 18px;margin-bottom:0}.feature-news-content-block:first-of-type{padding-top:14px}.feature-news-content-block+.feature-news-content-block{padding-top:12px}.feature-news-content-label{display:block;margin-bottom:8px;font-size:11px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:#64748b}.feature-news-content-panel{margin-bottom:14px;padding:12px 14px;border-radius:10px;background:#f8fafc;border:1px solid rgba(226,232,240,.95)}.feature-news-content-panel--detail{background:linear-gradient(180deg,#fafbfc 0%,#f1f5f9 100%)}.feature-news-item-summary,.feature-news-item-description{margin:0;font-size:14px;line-height:1.65;white-space:pre-line;color:#334155}.feature-news-item-description{color:#1e293b}.feature-news-item-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 18px 16px;margin-top:0;font-size:12px;font-weight:600;color:#64748b;border-top:1px solid rgba(241,245,249,.98);background:rgba(248,250,252,.65)}.feature-news-item-meta__date{font-variant-numeric:tabular-nums;color:#475569}.feature-news-item-meta__dot{width:4px;height:4px;border-radius:50%;background:linear-gradient(180deg,var(--fn-gold),#94a3b8)}.feature-news-item-meta__author{color:#334155}.feature-news-footer{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 22px 18px;border-top:1px solid rgba(226,232,240,.95);background:linear-gradient(180deg,#f8fafc 0%,#f1f5f9 100%)}.feature-news-footer-note{display:flex;align-items:flex-start;gap:10px;flex:1;min-width:0;font-size:12px;line-height:1.45;color:#64748b}.feature-news-footer-note__icon{flex-shrink:0;width:8px;height:8px;margin-top:4px;border-radius:50%;background:radial-gradient(circle at 30% 30%,#93c5fd,#2563eb);box-shadow:0 0 0 2px #2563eb26}.feature-news-confirm-btn{flex-shrink:0;min-width:128px;padding:10px 22px;border:none;border-radius:10px;font-size:14px;font-weight:700;letter-spacing:.02em;color:#fff;cursor:pointer;background:linear-gradient(180deg,#3b82f6 0%,#1d4ed8 48%,#1e3a8a 100%);box-shadow:0 1px #fff3 inset,0 8px 22px #2563eb59;transition:box-shadow .2s ease,transform .15s ease}.feature-news-confirm-btn:hover{box-shadow:0 1px #ffffff40 inset,0 10px 28px #2563eb6b}.feature-news-confirm-btn:focus{outline:none;box-shadow:0 1px #fff3 inset,0 0 0 3px #c9a22773,0 8px 22px #2563eb59}.feature-news-confirm-btn:active{transform:translateY(1px)}@media (max-width: 768px){.feature-news-overlay{padding:12px}.feature-news-dialog{max-height:90vh;border-radius:14px}.feature-news-header{padding:16px 16px 14px}.feature-news-brand{gap:10px}.feature-news-title-wrap h4{font-size:18px}.feature-news-subtitle{font-size:12px}.feature-news-body{padding:14px 14px 10px}.feature-news-item-head{flex-direction:column;align-items:flex-start;padding:14px 14px 10px}.feature-news-content-block{padding:0 14px}.feature-news-content-block:first-of-type{padding-top:12px}.feature-news-item-meta{padding:10px 14px 14px}.feature-news-footer{flex-direction:column;align-items:stretch;padding:12px 14px 16px;text-align:center}.feature-news-footer-note{justify-content:center;text-align:left}.feature-news-confirm-btn{width:100%}}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }] });
377
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: FeatureNewsDialogComponent, decorators: [{
378
+ type: Component,
379
+ args: [{ selector: 'app-feature-news-dialog', template: "<div class=\"feature-news-overlay\" *ngIf=\"dialogState\">\n <div class=\"feature-news-dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"feature-news-title\">\n <div class=\"feature-news-header\">\n <div class=\"feature-news-header__main\">\n <div class=\"feature-news-brand\">\n <div class=\"feature-news-title-wrap mt-2\">\n <span id=\"feature-news-title\" class=\"feature-news-chip\">C\u1EADp nh\u1EADt t\u00EDnh n\u0103ng</span>\n </div>\n </div>\n </div>\n <button type=\"button\" class=\"feature-news-close\" aria-label=\"Dong\" (click)=\"closeDialog()\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n\n <div class=\"feature-news-body\">\n <div class=\"feature-news-item\" *ngFor=\"let item of dialogState?.news\">\n <div class=\"feature-news-item-head\">\n <h5 class=\"feature-news-content-label mt-2\">{{ item?.title }}</h5>\n <span class=\"feature-news-item-tag\">M\u1EDBi nh\u1EA5t</span>\n </div>\n <div class=\"feature-news-content-block\" *ngIf=\"item?.summary\">\n <div class=\"feature-news-content-label mt-2\">T\u00F3m t\u1EAFt</div>\n <div class=\"feature-news-content-panel\">\n <p class=\"feature-news-item-summary\">{{ getSummary(item) }}</p>\n </div>\n </div>\n <div class=\"feature-news-content-block\" *ngIf=\"item?.description\">\n <div class=\"feature-news-content-label mt-2\">Chi ti\u1EBFt</div>\n <div class=\"feature-news-content-panel feature-news-content-panel--detail\">\n <p class=\"feature-news-item-description\">{{ item?.description }}</p>\n </div>\n </div>\n <div class=\"feature-news-item-meta\">\n <span class=\"feature-news-item-meta__dot\" *ngIf=\"item?.author?.name\" aria-hidden=\"true\"></span>\n <span class=\"feature-news-item-meta__date\">{{\n item?.createdOn | date : 'dd/MM/yyyy'\n }}</span>\n\n </div>\n </div>\n </div>\n\n <div class=\"feature-news-footer\">\n <div class=\"feature-news-footer-note\">\n <span class=\"feature-news-footer-note__icon\" aria-hidden=\"true\"></span>\n Th\u00F4ng b\u00E1o ch\u1EC9 hi\u1EC7n khi c\u00F3 tin m\u1EDBi; b\u1EA1n lu\u00F4n c\u00F3 th\u1EC3 xem l\u1EA1i t\u1EEB footer.\n </div>\n <button type=\"button\" class=\"feature-news-confirm-btn\" (click)=\"closeDialog()\">\n \u0110\u00E3 hi\u1EC3u\n </button>\n </div>\n </div>\n</div>", styles: ["@charset \"UTF-8\";.feature-news-overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;z-index:2000;padding:24px 16px;background:radial-gradient(ellipse 120% 80% at 50% 0%,rgba(30,58,95,.45) 0%,transparent 55%),rgba(15,23,42,.62);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}.feature-news-dialog{--fn-gold: #c9a227;--fn-gold-soft: rgba(201, 162, 39, .35);--fn-navy: #0f172a;--fn-navy-mid: #1e293b;position:relative;width:100%;max-width:720px;max-height:min(86vh,880px);display:flex;flex-direction:column;border-radius:16px;overflow:hidden;background:linear-gradient(180deg,#ffffff 0%,#f8fafc 100%);border:1px solid rgba(255,255,255,.65);box-shadow:0 0 0 1px #0f172a0f,0 24px 56px #0f172a38,0 2px #ffffffe6 inset}.feature-news-header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;padding:20px 22px 16px;background:linear-gradient(165deg,#f8fafc 0%,#ffffff 42%,rgba(248,250,252,.96) 100%);border-bottom:1px solid rgba(148,163,184,.28)}.feature-news-header__main{flex:1;min-width:0}.feature-news-brand{display:flex;gap:14px;align-items:flex-start}.feature-news-title-wrap{display:flex;flex-direction:column;gap:8px;min-width:0}.feature-news-chip{font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:#1d4ed8}.feature-news-title-wrap h4{margin:0;color:var(--fn-navy);font-size:21px;font-weight:800;letter-spacing:-.02em;line-height:1.25}.feature-news-subtitle{margin:0;color:#64748b;font-size:13px;line-height:1.5;max-width:52ch}.feature-news-close{flex-shrink:0;width:38px;height:38px;border:1px solid rgba(148,163,184,.35);border-radius:12px;background:linear-gradient(180deg,#ffffff 0%,#f1f5f9 100%);color:#64748b;font-size:22px;line-height:1;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:color .2s ease,border-color .2s ease,box-shadow .2s ease}.feature-news-close:hover{color:var(--fn-navy);border-color:#2563eb59;box-shadow:0 4px 14px #2563eb1f}.feature-news-close:focus{outline:none;box-shadow:0 0 0 3px #93c5fd8c}.feature-news-body{flex:1 1 auto;overflow-y:auto;padding:18px 22px 14px;background:linear-gradient(180deg,#f8fafc 0%,#ffffff 100%);-webkit-overflow-scrolling:touch}.feature-news-item{border-radius:14px;padding:0;margin-bottom:0;background:#ffffff;border:1px solid rgba(148,163,184,.22);box-shadow:0 1px #ffffffe6 inset,0 8px 28px #0f172a0f;overflow:hidden}.feature-news-item-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px 18px 12px;background:linear-gradient(180deg,rgba(248,250,252,.9) 0%,#ffffff 100%);border-bottom:1px solid rgba(226,232,240,.9)}.feature-news-item-title{margin:0;font-size:18px;font-weight:800;color:var(--fn-navy);letter-spacing:-.02em;line-height:1.3}.feature-news-item-tag{flex-shrink:0;padding:5px 11px;border-radius:999px;font-size:10px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:#1e40af;background:rgba(239,246,255,.95);border:1px solid rgba(191,219,254,.95)}.feature-news-content-block{padding:0 18px;margin-bottom:0}.feature-news-content-block:first-of-type{padding-top:14px}.feature-news-content-block+.feature-news-content-block{padding-top:12px}.feature-news-content-label{display:block;margin-bottom:8px;font-size:11px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:#64748b}.feature-news-content-panel{margin-bottom:14px;padding:12px 14px;border-radius:10px;background:#f8fafc;border:1px solid rgba(226,232,240,.95)}.feature-news-content-panel--detail{background:linear-gradient(180deg,#fafbfc 0%,#f1f5f9 100%)}.feature-news-item-summary,.feature-news-item-description{margin:0;font-size:14px;line-height:1.65;white-space:pre-line;color:#334155}.feature-news-item-description{color:#1e293b}.feature-news-item-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:12px 18px 16px;margin-top:0;font-size:12px;font-weight:600;color:#64748b;border-top:1px solid rgba(241,245,249,.98);background:rgba(248,250,252,.65)}.feature-news-item-meta__date{font-variant-numeric:tabular-nums;color:#475569}.feature-news-item-meta__dot{width:4px;height:4px;border-radius:50%;background:linear-gradient(180deg,var(--fn-gold),#94a3b8)}.feature-news-item-meta__author{color:#334155}.feature-news-footer{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 22px 18px;border-top:1px solid rgba(226,232,240,.95);background:linear-gradient(180deg,#f8fafc 0%,#f1f5f9 100%)}.feature-news-footer-note{display:flex;align-items:flex-start;gap:10px;flex:1;min-width:0;font-size:12px;line-height:1.45;color:#64748b}.feature-news-footer-note__icon{flex-shrink:0;width:8px;height:8px;margin-top:4px;border-radius:50%;background:radial-gradient(circle at 30% 30%,#93c5fd,#2563eb);box-shadow:0 0 0 2px #2563eb26}.feature-news-confirm-btn{flex-shrink:0;min-width:128px;padding:10px 22px;border:none;border-radius:10px;font-size:14px;font-weight:700;letter-spacing:.02em;color:#fff;cursor:pointer;background:linear-gradient(180deg,#3b82f6 0%,#1d4ed8 48%,#1e3a8a 100%);box-shadow:0 1px #fff3 inset,0 8px 22px #2563eb59;transition:box-shadow .2s ease,transform .15s ease}.feature-news-confirm-btn:hover{box-shadow:0 1px #ffffff40 inset,0 10px 28px #2563eb6b}.feature-news-confirm-btn:focus{outline:none;box-shadow:0 1px #fff3 inset,0 0 0 3px #c9a22773,0 8px 22px #2563eb59}.feature-news-confirm-btn:active{transform:translateY(1px)}@media (max-width: 768px){.feature-news-overlay{padding:12px}.feature-news-dialog{max-height:90vh;border-radius:14px}.feature-news-header{padding:16px 16px 14px}.feature-news-brand{gap:10px}.feature-news-title-wrap h4{font-size:18px}.feature-news-subtitle{font-size:12px}.feature-news-body{padding:14px 14px 10px}.feature-news-item-head{flex-direction:column;align-items:flex-start;padding:14px 14px 10px}.feature-news-content-block{padding:0 14px}.feature-news-content-block:first-of-type{padding-top:12px}.feature-news-item-meta{padding:10px 14px 14px}.feature-news-footer{flex-direction:column;align-items:stretch;padding:12px 14px 16px;text-align:center}.feature-news-footer-note{justify-content:center;text-align:left}.feature-news-confirm-btn{width:100%}}\n"] }]
380
+ }] });
381
+
382
+ /**
383
+ * Import module này ở shell layout (hoặc `AppModule`) để có `<app-feature-news-dialog />`.
384
+ * Nhớ cung cấp `EIU_APP_API_BASE_URL` trong providers.
385
+ */
386
+ class EiuAppFeatureNewsModule {
387
+ }
388
+ EiuAppFeatureNewsModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFeatureNewsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
389
+ EiuAppFeatureNewsModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFeatureNewsModule, declarations: [FeatureNewsDialogComponent], imports: [CommonModule], exports: [FeatureNewsDialogComponent] });
390
+ EiuAppFeatureNewsModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFeatureNewsModule, imports: [CommonModule] });
391
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: EiuAppFeatureNewsModule, decorators: [{
392
+ type: NgModule,
393
+ args: [{
394
+ imports: [CommonModule],
395
+ declarations: [FeatureNewsDialogComponent],
396
+ exports: [FeatureNewsDialogComponent]
397
+ }]
398
+ }] });
399
+
400
+ /*
401
+ * EIU App Kit — tái sử dụng giữa các Angular app (feature news Redmine, launcher header, …)
402
+ */
403
+
404
+ /**
405
+ * Generated bundle index. Do not edit.
406
+ */
407
+
408
+ export { EIU_APP_API_BASE_URL, EIU_APP_LAUNCHER_MENU_LOADER, EiuAppFeatureNewsModule, EiuAppFooterComponent, EiuAppFooterModule, EiuAppLauncherComponent, EiuAppLauncherModule, EiuContactSupportComponent, EiuSidebarLogoComponent, EiuSidebarSharedModule, FEATURE_NEWS_REDMINE_PROJECT_IDENTIFIER, FEATURE_NEWS_SEEN_STORAGE_KEY_PREFIX, FeatureNewsDialogComponent, FeatureNewsDialogService, FeatureNewsService, NewsApiService };
409
+ //# sourceMappingURL=eiu-app-kit.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eiu-app-kit.mjs","sources":["../../../projects/eiu-app-kit/src/lib/eiu-app-kit.tokens.ts","../../../projects/eiu-app-kit/src/lib/app-launcher/eiu-app-launcher-menu.token.ts","../../../projects/eiu-app-kit/src/lib/app-launcher/eiu-app-launcher.component.ts","../../../projects/eiu-app-kit/src/lib/app-launcher/eiu-app-launcher.component.html","../../../projects/eiu-app-kit/src/lib/app-launcher/eiu-app-launcher.module.ts","../../../projects/eiu-app-kit/src/lib/sidebar-shared/logo/eiu-sidebar-logo.component.ts","../../../projects/eiu-app-kit/src/lib/sidebar-shared/logo/eiu-sidebar-logo.component.html","../../../projects/eiu-app-kit/src/lib/sidebar-shared/contact/eiu-contact-support.component.ts","../../../projects/eiu-app-kit/src/lib/sidebar-shared/contact/eiu-contact-support.component.html","../../../projects/eiu-app-kit/src/lib/sidebar-shared/eiu-sidebar-shared.module.ts","../../../projects/eiu-app-kit/src/lib/footer/eiu-app-footer.component.ts","../../../projects/eiu-app-kit/src/lib/footer/eiu-app-footer.component.html","../../../projects/eiu-app-kit/src/lib/footer/eiu-app-footer.module.ts","../../../projects/eiu-app-kit/src/lib/feature-news/constants.ts","../../../projects/eiu-app-kit/src/lib/feature-news/news-api.service.ts","../../../projects/eiu-app-kit/src/lib/feature-news/feature-news-dialog.service.ts","../../../projects/eiu-app-kit/src/lib/feature-news/feature-news.service.ts","../../../projects/eiu-app-kit/src/lib/feature-news/feature-news-dialog.component.ts","../../../projects/eiu-app-kit/src/lib/feature-news/feature-news-dialog.component.html","../../../projects/eiu-app-kit/src/lib/feature-news/eiu-app-feature-news.module.ts","../../../projects/eiu-app-kit/src/public-api.ts","../../../projects/eiu-app-kit/src/eiu-app-kit.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\r\n\r\n/**\r\n * Base URL API EIU App (ví dụ `environment.API_EIU_APP`).\r\n * Dùng cho News API, icon launcher, … — cung cấp một lần trong `AppModule`.\r\n */\r\nexport const EIU_APP_API_BASE_URL = new InjectionToken<string>(\r\n 'EIU_APP_API_BASE_URL'\r\n);\r\n","import { InjectionToken } from '@angular/core';\r\nimport { Observable } from 'rxjs';\r\nimport { EiuAppLauncherMenuItem } from './eiu-app-launcher-menu.model';\r\n\r\nexport interface EiuAppLauncherMenuLoader {\r\n loadItems(): Observable<EiuAppLauncherMenuItem[]>;\r\n}\r\n\r\n/**\r\n * Host app cung cấp cách load danh sách ứng dụng (ví dụ `ProjectService.getAllProject()`).\r\n */\r\nexport const EIU_APP_LAUNCHER_MENU_LOADER = new InjectionToken<EiuAppLauncherMenuLoader>(\r\n 'EIU_APP_LAUNCHER_MENU_LOADER'\r\n);\r\n","import {\n AfterViewInit,\n ChangeDetectorRef,\n Component,\n EventEmitter,\n HostListener,\n Inject,\n OnInit,\n Input,\n Output\n} from '@angular/core';\nimport { EIU_APP_API_BASE_URL } from '../eiu-app-kit.tokens';\nimport {\n EIU_APP_LAUNCHER_MENU_LOADER,\n EiuAppLauncherMenuLoader\n} from './eiu-app-launcher-menu.token';\n\nexport interface EiuAppLauncherViewItem {\n label: string;\n icon: string;\n link: string;\n isActive: boolean;\n}\n\n@Component({\n selector: 'app-eiu-app-launcher',\n templateUrl: './eiu-app-launcher.component.html',\n styleUrls: ['./eiu-app-launcher.component.css']\n})\nexport class EiuAppLauncherComponent implements OnInit, AfterViewInit {\n /** Phát ra khi user bấm icon menu (sidebar). Host gắn với store / layout. */\n @Output() readonly sidebarToggle = new EventEmitter<void>();\n @Input() appLabel = 'Ứng dụng';\n\n isScreenWide = true;\n menuItems: EiuAppLauncherViewItem[] = [];\n\n private readonly _apiBase: string;\n\n constructor(\n @Inject(EIU_APP_LAUNCHER_MENU_LOADER)\n private readonly _menuLoader: EiuAppLauncherMenuLoader,\n @Inject(EIU_APP_API_BASE_URL) apiBaseUrl: string,\n private readonly _cdr: ChangeDetectorRef\n ) {\n this._apiBase = (apiBaseUrl || '').replace(/\\/$/, '');\n }\n\n ngOnInit(): void {\n this._loadMenu();\n }\n\n @HostListener('window:resize')\n onResize(): void {\n this._adjustMenuPosition();\n }\n\n ngAfterViewInit(): void {\n this._adjustMenuPosition();\n this._cdr.detectChanges();\n }\n\n onToggleMenuSidebar(): void {\n this.sidebarToggle.emit();\n }\n\n private _adjustMenuPosition(): void {\n this.isScreenWide = window.innerWidth > 1300;\n }\n\n private _loadMenu(): void {\n this._menuLoader.loadItems().subscribe({\n next: (res) => {\n this.menuItems = (res ?? [])\n .filter((x) => x.link)\n .sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))\n .map((x) => ({\n label: x.name_VI ?? '',\n icon: this._iconUrl(x.imageUrl),\n link: x.link as string,\n isActive: this._isActive(x.link as string)\n }));\n },\n error: (err) => console.error(err)\n });\n }\n\n private _iconUrl(imageUrl: string | null): string {\n if (!imageUrl) {\n return '';\n }\n if (/^https?:\\/\\//i.test(imageUrl)) {\n return imageUrl;\n }\n const path = imageUrl.replace(/^\\//, '');\n return this._apiBase ? `${this._apiBase}/${path}` : path;\n }\n\n private _isActive(link: string): boolean {\n try {\n const currentDomain = new URL(window.location.href).origin;\n const linkDomain = new URL(link).origin;\n return currentDomain === linkDomain;\n } catch {\n return false;\n }\n }\n\n}\n","<ul class=\"navbar-nav\">\n <li class=\"nav-item\">\n <a class=\"nav-link\" href=\"#\" (click)=\"onToggleMenuSidebar(); $event.preventDefault()\" role=\"button\"><i\n class=\"fas fa-bars\"></i></a>\n </li>\n <ng-container *ngIf=\"isScreenWide\">\n <li class=\"nav-item\" *ngFor=\"let menuItem of menuItems | slice: 0:5\">\n <a class=\"nav-link\" href=\"{{ menuItem.link }}\" [ngClass]=\"{ active: menuItem.isActive }\" target=\"_blank\">\n {{ menuItem.label }}\n </a>\n </li>\n </ng-container>\n <li class=\"nav-item dropdown\">\n <a class=\"nav-link dropdown-toggle\" role=\"button\" aria-expanded=\"false\">\n {{ appLabel }}\n </a>\n\n <!-- Mega Menu -->\n <div class=\"mega-dropdown\" id=\"megaDropdown\">\n <div class=\"menu-item-card\" *ngFor=\"let menuItem of menuItems\">\n <a class=\"dropdown-item\" href=\"{{ menuItem.link }}\" [ngClass]=\"{ active: menuItem.isActive }\"\n target=\"_blank\">\n <div class=\"icon-container\">\n <img [src]=\"menuItem.icon\" alt=\"\" height=\"25\" width=\"25\" />\n </div>\n <span>{{ menuItem.label }}</span>\n </a>\n </div>\n </div>\n </li>\n</ul>","import { CommonModule } from '@angular/common';\r\nimport { NgModule } from '@angular/core';\r\nimport { EiuAppLauncherComponent } from './eiu-app-launcher.component';\r\n\r\n/**\r\n * Import ở shell layout (ví dụ `ThemeModule`).\r\n * Cần `EIU_APP_API_BASE_URL` và `EIU_APP_LAUNCHER_MENU_LOADER` trong `providers`.\r\n */\r\n@NgModule({\r\n imports: [CommonModule],\r\n declarations: [EiuAppLauncherComponent],\r\n exports: [EiuAppLauncherComponent]\r\n})\r\nexport class EiuAppLauncherModule { }\r\n","import { Component, Input } from '@angular/core';\r\n\r\n@Component({\r\n selector: 'app-eiu-sidebar-logo',\r\n templateUrl: './eiu-sidebar-logo.component.html',\r\n styleUrls: ['./eiu-sidebar-logo.component.css']\r\n})\r\nexport class EiuSidebarLogoComponent {\r\n @Input() imageUrl = '';\r\n @Input() alt = 'Đại học Quốc tế Miền Đông (EIU)';\r\n @Input() text = 'Đại học Quốc tế Miền Đông (EIU)';\r\n @Input() homeLink = '/';\r\n}\r\n","<a [routerLink]=\"[homeLink]\" class=\"brand-link\">\r\n <img\r\n class=\"brand-image\"\r\n [src]=\"imageUrl\"\r\n [alt]=\"alt\"\r\n height=\"40\"\r\n />\r\n <strong class=\"brand-text font-weight-light\">{{ text }}</strong>\r\n</a>\r\n","import { Component, Input } from '@angular/core';\nimport { EiuAppSidebarContactItem } from '../sidebar-shared.model';\n\n@Component({\n selector: 'app-eiu-contact-support',\n templateUrl: './eiu-contact-support.component.html',\n styleUrls: ['./eiu-contact-support.component.css']\n})\nexport class EiuContactSupportComponent {\n @Input() title = 'Liên hệ';\n @Input() contacts: EiuAppSidebarContactItem[] = [];\n\n get sortedContacts(): EiuAppSidebarContactItem[] {\n return [...(this.contacts || [])].sort(\n (a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0)\n );\n }\n}\n","<div class=\"box-contact\">\n <div class=\"header-contact\"></div>\n <li class=\"nav-header\">\n <strong class=\"contact-title\">\n <i class=\"nav-icon fa fa-headphones\"></i>&nbsp;&nbsp;{{ title }}\n </strong>\n </li>\n <li class=\"nav-item\" *ngFor=\"let item of sortedContacts\">\n <div class=\"nav-link contact-content\">\n <p class=\"text contact-name\">{{ item?.fullName }}</p>\n <div class=\"contact-line\">\n <a class=\"text-sm contact-link\" [href]=\"'tel:' + item?.phoneNumber\" [title]=\"item?.phoneNumber\">\n <span class=\"contact-meta\">M: {{ item?.phoneNumber }}</span>\n </a>\n </div>\n <div class=\"contact-line\">\n <a class=\"text-sm contact-link\" [href]=\"'mailto:' + item?.email\" [title]=\"item?.email\">\n <span class=\"contact-meta\">E: {{ item?.email }}</span>\n </a>\n </div>\n </div>\n </li>\n</div>","import { CommonModule } from '@angular/common';\r\nimport { NgModule } from '@angular/core';\r\nimport { RouterModule } from '@angular/router';\r\nimport { EiuContactSupportComponent } from './contact/eiu-contact-support.component';\r\nimport { EiuSidebarLogoComponent } from './logo/eiu-sidebar-logo.component';\r\n\r\n@NgModule({\r\n imports: [CommonModule, RouterModule],\r\n declarations: [EiuSidebarLogoComponent, EiuContactSupportComponent],\r\n exports: [EiuSidebarLogoComponent, EiuContactSupportComponent]\r\n})\r\nexport class EiuSidebarSharedModule { }\r\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\r\n\r\n@Component({\r\n selector: 'app-eiu-app-footer',\r\n templateUrl: './eiu-app-footer.component.html',\r\n styleUrls: ['./eiu-app-footer.component.css']\r\n})\r\nexport class EiuAppFooterComponent {\r\n @Input() appVersion = '';\r\n @Input() copyrightYear = '2023';\r\n @Input() companyName = 'IT - EIU';\r\n @Input() companyUrl = 'https://sites.google.com/eiu.edu.vn/oit/';\r\n @Input() allRightsText = 'All rights reserved.';\r\n @Input() ringLabel = 'Phiên bản';\r\n @Output() readonly ringClick = new EventEmitter<void>();\r\n\r\n onRingClick(event: Event): void {\r\n event.preventDefault();\r\n this.ringClick.emit();\r\n }\r\n}\r\n","<div class=\"float-right d-none d-sm-block footer-version-wrap\">\r\n <a href=\"#\" class=\"radar-ring\" (click)=\"onRingClick($event)\">\r\n <span>{{ ringLabel }}</span>\r\n </a>\r\n <b>Version</b> {{ appVersion }}\r\n</div>\r\n<strong>\r\n <span>Copyright &copy; {{ copyrightYear }}</span>\r\n <a [href]=\"companyUrl\" target=\"_blank\" rel=\"noopener\" style=\"margin: 0\">\r\n {{ companyName }}</a\r\n >\r\n <span>.</span>\r\n</strong>\r\n<span> {{ allRightsText }}</span>\r\n","import { CommonModule } from '@angular/common';\r\nimport { NgModule } from '@angular/core';\r\nimport { EiuAppFooterComponent } from './eiu-app-footer.component';\r\n\r\n@NgModule({\r\n imports: [CommonModule],\r\n declarations: [EiuAppFooterComponent],\r\n exports: [EiuAppFooterComponent]\r\n})\r\nexport class EiuAppFooterModule {}\r\n","/** Prefix localStorage: feature_news_seen::<userId>::<projectIdentifier> */\r\nexport const FEATURE_NEWS_SEEN_STORAGE_KEY_PREFIX = 'feature_news_seen::';\r\n\r\n/**\r\n * Mặc định project identifier trên Redmine (EIU App).\r\n * Ứng dụng host có thể dùng hằng này hoặc chuỗi riêng khi gọi service.\r\n */\r\nexport const FEATURE_NEWS_REDMINE_PROJECT_IDENTIFIER =\r\n 'login-sso-quan-ly-nhan-su';\r\n","import { HttpClient, HttpParams } from '@angular/common/http';\r\nimport { Injectable, inject } from '@angular/core';\r\nimport { Observable, catchError, of } from 'rxjs';\r\nimport { EIU_APP_API_BASE_URL } from '../eiu-app-kit.tokens';\r\nimport { OperationResult } from './models/operation-result.model';\r\nimport { RedmineNewsResponse } from './models/redmine-news.model';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class NewsApiService {\r\n private readonly _httpClient = inject(HttpClient);\r\n private readonly _apiBaseUrl = inject(EIU_APP_API_BASE_URL);\r\n\r\n getNewsByProjectIdentifier(\r\n projectIdentifier: string,\r\n limit = 1\r\n ): Observable<OperationResult<RedmineNewsResponse>> {\r\n const params = new HttpParams()\r\n .set('projectIdentifier', projectIdentifier)\r\n .set('limit', String(limit));\r\n const url = `${this._apiBaseUrl}/News/GetRedmineNewsByProjectIdentifier`;\r\n\r\n return this._httpClient\r\n .get<OperationResult<RedmineNewsResponse>>(url, { params })\r\n .pipe(\r\n catchError((error: unknown) =>\r\n of({\r\n success: false,\r\n statusCode: 500,\r\n message:\r\n error instanceof Error\r\n ? error.message\r\n : 'Cannot get feature news from API.',\r\n data: null\r\n })\r\n )\r\n );\r\n }\r\n}\r\n","import { Injectable } from '@angular/core';\r\nimport { BehaviorSubject, Observable, Subject, take } from 'rxjs';\r\nimport { RedmineNewsItem } from './models/redmine-news.model';\r\n\r\nexport interface FeatureNewsDialogState {\r\n news: RedmineNewsItem[];\r\n}\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class FeatureNewsDialogService {\r\n private readonly _dialogState = new BehaviorSubject<FeatureNewsDialogState | null>(\r\n null\r\n );\r\n private _closeNotifier: Subject<void> | null = null;\r\n\r\n readonly dialogState$ = this._dialogState.asObservable();\r\n\r\n open(news: RedmineNewsItem[]): Observable<void> {\r\n this._closeNotifier = new Subject<void>();\r\n this._dialogState.next({ news });\r\n return this._closeNotifier.asObservable().pipe(take(1));\r\n }\r\n\r\n close(): void {\r\n this._dialogState.next(null);\r\n this._closeNotifier?.next();\r\n this._closeNotifier?.complete();\r\n this._closeNotifier = null;\r\n }\r\n}\r\n","import { Injectable, inject } from '@angular/core';\r\nimport { Observable, catchError, map, of, switchMap, tap } from 'rxjs';\r\nimport { FEATURE_NEWS_SEEN_STORAGE_KEY_PREFIX } from './constants';\r\nimport { FeatureNewsDialogService } from './feature-news-dialog.service';\r\nimport { NewsApiService } from './news-api.service';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class FeatureNewsService {\r\n private readonly _newsApiService = inject(NewsApiService);\r\n private readonly _featureNewsDialogService = inject(FeatureNewsDialogService);\r\n\r\n buildSeenKey(userId: string, projectIdentifier: string): string {\r\n return `${FEATURE_NEWS_SEEN_STORAGE_KEY_PREFIX}${userId}::${projectIdentifier}`;\r\n }\r\n\r\n hasSeenLatestNews(\r\n userId: string,\r\n projectIdentifier: string,\r\n latestNewsId: number\r\n ): boolean {\r\n const key = this.buildSeenKey(userId, projectIdentifier);\r\n return localStorage.getItem(key) === String(latestNewsId);\r\n }\r\n\r\n markSeen(userId: string, projectIdentifier: string, latestNewsId: number): void {\r\n const key = this.buildSeenKey(userId, projectIdentifier);\r\n localStorage.setItem(key, String(latestNewsId));\r\n }\r\n\r\n checkAndShowFeatureNews(\r\n userId: string,\r\n projectIdentifier: string\r\n ): Observable<void> {\r\n return this._newsApiService.getNewsByProjectIdentifier(projectIdentifier, 1).pipe(\r\n switchMap((result) => {\r\n const news = result.success ? (result.data?.news ?? []).slice(0, 1) : [];\r\n if (!news.length) {\r\n return of(void 0);\r\n }\r\n const latestNews = news[0];\r\n if (this.hasSeenLatestNews(userId, projectIdentifier, latestNews.id)) {\r\n return of(void 0);\r\n }\r\n\r\n return this._featureNewsDialogService.open(news).pipe(\r\n tap(() => this.markSeen(userId, projectIdentifier, latestNews.id)),\r\n map(() => void 0)\r\n );\r\n }),\r\n catchError(() => of(void 0))\r\n );\r\n }\r\n\r\n showLatestFeatureNewsManually(\r\n userId: string,\r\n projectIdentifier: string\r\n ): Observable<void> {\r\n return this._newsApiService.getNewsByProjectIdentifier(projectIdentifier, 1).pipe(\r\n switchMap((result) => {\r\n const news = result.success ? (result.data?.news ?? []).slice(0, 1) : [];\r\n if (!news.length) {\r\n return of(void 0);\r\n }\r\n const latestNews = news[0];\r\n return this._featureNewsDialogService.open(news).pipe(\r\n tap(() => this.markSeen(userId, projectIdentifier, latestNews.id)),\r\n map(() => void 0)\r\n );\r\n }),\r\n catchError(() => of(void 0))\r\n );\r\n }\r\n}\r\n","import { Component, OnDestroy, OnInit, inject } from '@angular/core';\nimport { Subscription } from 'rxjs';\nimport { RedmineNewsItem } from './models/redmine-news.model';\nimport {\n FeatureNewsDialogService,\n FeatureNewsDialogState\n} from './feature-news-dialog.service';\n\n@Component({\n selector: 'app-feature-news-dialog',\n templateUrl: './feature-news-dialog.component.html',\n styleUrls: ['./feature-news-dialog.component.scss']\n})\nexport class FeatureNewsDialogComponent implements OnInit, OnDestroy {\n private readonly _featureNewsDialogService = inject(FeatureNewsDialogService);\n private _subscription: Subscription | null = null;\n protected dialogState: FeatureNewsDialogState | null = null;\n\n ngOnInit(): void {\n this._subscription = this._featureNewsDialogService.dialogState$.subscribe(\n (state) => {\n this.dialogState = state;\n }\n );\n }\n\n ngOnDestroy(): void {\n this._subscription?.unsubscribe();\n }\n\n getSummary(item: RedmineNewsItem): string {\n return this._truncateText(item.summary || '', 200);\n }\n\n closeDialog(): void {\n this._featureNewsDialogService.close();\n }\n\n private _truncateText(text: string, maxLength: number): string {\n if (!text) {\n return '';\n }\n if (text.length <= maxLength) {\n return text;\n }\n return `${text.slice(0, maxLength)}...`;\n }\n}\n","<div class=\"feature-news-overlay\" *ngIf=\"dialogState\">\n <div class=\"feature-news-dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"feature-news-title\">\n <div class=\"feature-news-header\">\n <div class=\"feature-news-header__main\">\n <div class=\"feature-news-brand\">\n <div class=\"feature-news-title-wrap mt-2\">\n <span id=\"feature-news-title\" class=\"feature-news-chip\">Cập nhật tính năng</span>\n </div>\n </div>\n </div>\n <button type=\"button\" class=\"feature-news-close\" aria-label=\"Dong\" (click)=\"closeDialog()\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n </div>\n\n <div class=\"feature-news-body\">\n <div class=\"feature-news-item\" *ngFor=\"let item of dialogState?.news\">\n <div class=\"feature-news-item-head\">\n <h5 class=\"feature-news-content-label mt-2\">{{ item?.title }}</h5>\n <span class=\"feature-news-item-tag\">Mới nhất</span>\n </div>\n <div class=\"feature-news-content-block\" *ngIf=\"item?.summary\">\n <div class=\"feature-news-content-label mt-2\">Tóm tắt</div>\n <div class=\"feature-news-content-panel\">\n <p class=\"feature-news-item-summary\">{{ getSummary(item) }}</p>\n </div>\n </div>\n <div class=\"feature-news-content-block\" *ngIf=\"item?.description\">\n <div class=\"feature-news-content-label mt-2\">Chi tiết</div>\n <div class=\"feature-news-content-panel feature-news-content-panel--detail\">\n <p class=\"feature-news-item-description\">{{ item?.description }}</p>\n </div>\n </div>\n <div class=\"feature-news-item-meta\">\n <span class=\"feature-news-item-meta__dot\" *ngIf=\"item?.author?.name\" aria-hidden=\"true\"></span>\n <span class=\"feature-news-item-meta__date\">{{\n item?.createdOn | date : 'dd/MM/yyyy'\n }}</span>\n\n </div>\n </div>\n </div>\n\n <div class=\"feature-news-footer\">\n <div class=\"feature-news-footer-note\">\n <span class=\"feature-news-footer-note__icon\" aria-hidden=\"true\"></span>\n Thông báo chỉ hiện khi có tin mới; bạn luôn có thể xem lại từ footer.\n </div>\n <button type=\"button\" class=\"feature-news-confirm-btn\" (click)=\"closeDialog()\">\n Đã hiểu\n </button>\n </div>\n </div>\n</div>","import { CommonModule } from '@angular/common';\r\nimport { NgModule } from '@angular/core';\r\nimport { FeatureNewsDialogComponent } from './feature-news-dialog.component';\r\n\r\n/**\r\n * Import module này ở shell layout (hoặc `AppModule`) để có `<app-feature-news-dialog />`.\r\n * Nhớ cung cấp `EIU_APP_API_BASE_URL` trong providers.\r\n */\r\n@NgModule({\r\n imports: [CommonModule],\r\n declarations: [FeatureNewsDialogComponent],\r\n exports: [FeatureNewsDialogComponent]\r\n})\r\nexport class EiuAppFeatureNewsModule {}\r\n","/*\n * EIU App Kit — tái sử dụng giữa các Angular app (feature news Redmine, launcher header, …)\n */\n\nexport * from './lib/eiu-app-kit.tokens';\nexport * from './lib/app-launcher/eiu-app-launcher-menu.model';\nexport * from './lib/app-launcher/eiu-app-launcher-menu.token';\nexport * from './lib/app-launcher/eiu-app-launcher.component';\nexport * from './lib/app-launcher/eiu-app-launcher.module';\nexport * from './lib/sidebar-shared/sidebar-shared.model';\nexport * from './lib/sidebar-shared/logo/eiu-sidebar-logo.component';\nexport * from './lib/sidebar-shared/contact/eiu-contact-support.component';\nexport * from './lib/sidebar-shared/eiu-sidebar-shared.module';\nexport * from './lib/footer/eiu-app-footer.component';\nexport * from './lib/footer/eiu-app-footer.module';\nexport * from './lib/feature-news/constants';\nexport * from './lib/feature-news/models/operation-result.model';\nexport * from './lib/feature-news/models/redmine-news.model';\nexport * from './lib/feature-news/news-api.service';\nexport * from './lib/feature-news/feature-news-dialog.service';\nexport * from './lib/feature-news/feature-news.service';\nexport * from './lib/feature-news/feature-news-dialog.component';\nexport * from './lib/feature-news/eiu-app-feature-news.module';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1"],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="eiu-app-kit" />
5
+ export * from './public-api';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dữ liệu tối thiểu để hiển thị một mục trong launcher (mega menu).
3
+ * Khớp với payload thường gặp từ API Project (name_VI, imageUrl, …).
4
+ */
5
+ export interface EiuAppLauncherMenuItem {
6
+ sortOrder: number | null;
7
+ name_VI: string | null;
8
+ imageUrl: string | null;
9
+ link: string | null;
10
+ }
@@ -0,0 +1,10 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ import { Observable } from 'rxjs';
3
+ import { EiuAppLauncherMenuItem } from './eiu-app-launcher-menu.model';
4
+ export interface EiuAppLauncherMenuLoader {
5
+ loadItems(): Observable<EiuAppLauncherMenuItem[]>;
6
+ }
7
+ /**
8
+ * Host app cung cấp cách load danh sách ứng dụng (ví dụ `ProjectService.getAllProject()`).
9
+ */
10
+ export declare const EIU_APP_LAUNCHER_MENU_LOADER: InjectionToken<EiuAppLauncherMenuLoader>;