@web-portal/view-shared 0.0.1
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.
- package/fesm2022/web-portal-view-shared.mjs +615 -0
- package/fesm2022/web-portal-view-shared.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/UnsubscribeOnDestroyAdapter.d.ts +20 -0
- package/lib/base.store.d.ts +28 -0
- package/lib/components/excel-upload/excel-upload.component.d.ts +25 -0
- package/lib/components/index.d.ts +4 -0
- package/lib/components/media-upload/media-upload.component.d.ts +42 -0
- package/lib/components/relogin-dialog/relogin-dialog.component.d.ts +10 -0
- package/lib/components/tree-select/tree-select.component.d.ts +21 -0
- package/lib/components/tree-select/tree-select.interface.d.ts +13 -0
- package/lib/services/auth/auth-dialog.service.d.ts +9 -0
- package/lib/sub-sink.d.ts +45 -0
- package/package.json +27 -0
- package/public-api.d.ts +6 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
import { HttpEventType, HttpResponse } from '@angular/common/http';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { EventEmitter, Output, ViewChild, Component, inject, Input, signal, Injectable, computed } from '@angular/core';
|
|
4
|
+
import * as i1$2 from '@angular/forms';
|
|
5
|
+
import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
|
6
|
+
import * as i1$1 from '@angular/common';
|
|
7
|
+
import { CommonModule } from '@angular/common';
|
|
8
|
+
import * as i3 from 'primeng/button';
|
|
9
|
+
import { ButtonModule } from 'primeng/button';
|
|
10
|
+
import * as i4 from 'primeng/progressbar';
|
|
11
|
+
import { ProgressBarModule } from 'primeng/progressbar';
|
|
12
|
+
import { InputTextModule } from 'primeng/inputtext';
|
|
13
|
+
import * as i1 from '@web-portal/core-infrastructure';
|
|
14
|
+
import { FileWAPGCSService } from '@web-portal/core-infrastructure';
|
|
15
|
+
import * as i2 from 'primeng/fileupload';
|
|
16
|
+
import { FileUploadModule } from 'primeng/fileupload';
|
|
17
|
+
import * as i3$1 from 'primeng/api';
|
|
18
|
+
import { MessageService } from 'primeng/api';
|
|
19
|
+
import * as i2$1 from 'primeng/treeselect';
|
|
20
|
+
import { TreeSelectModule } from 'primeng/treeselect';
|
|
21
|
+
import { Subject } from 'rxjs';
|
|
22
|
+
import * as i1$3 from 'primeng/dynamicdialog';
|
|
23
|
+
import { DialogService } from 'primeng/dynamicdialog';
|
|
24
|
+
import { signalStoreFeature, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
|
|
25
|
+
|
|
26
|
+
class ExcelUploadComponent {
|
|
27
|
+
ngOnInit() { }
|
|
28
|
+
constructor(fileService, cdr, ngZone) {
|
|
29
|
+
this.fileService = fileService;
|
|
30
|
+
this.cdr = cdr;
|
|
31
|
+
this.ngZone = ngZone;
|
|
32
|
+
this.progress = 0;
|
|
33
|
+
this.message = "";
|
|
34
|
+
this.filePath = "";
|
|
35
|
+
this.absoluteURL = "";
|
|
36
|
+
this.onChange = new EventEmitter();
|
|
37
|
+
this.fileAttr = "Chọn file tải lên";
|
|
38
|
+
}
|
|
39
|
+
handleInputChange(event) {
|
|
40
|
+
const file = event.target.files?.[0];
|
|
41
|
+
if (!file) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.selectedFiles = null;
|
|
45
|
+
this.progress = 0;
|
|
46
|
+
this.currentFile = file;
|
|
47
|
+
this.fileAttr = file?.name;
|
|
48
|
+
this.fileService.uploadExcel(this.currentFile).subscribe({
|
|
49
|
+
next: (value) => {
|
|
50
|
+
if (value.type === HttpEventType.UploadProgress) {
|
|
51
|
+
// update progress on next tick to avoid ExpressionChanged errors
|
|
52
|
+
const percent = Math.round((100 * value.loaded) / value.total);
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
this.ngZone.run(() => {
|
|
55
|
+
this.progress = percent;
|
|
56
|
+
this.cdr.detectChanges();
|
|
57
|
+
});
|
|
58
|
+
}, 0);
|
|
59
|
+
}
|
|
60
|
+
else if (value instanceof HttpResponse) {
|
|
61
|
+
// emit and ensure view is stable before next change detection
|
|
62
|
+
this.onChange.emit(value.body.data);
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
this.ngZone.run(() => this.cdr.detectChanges());
|
|
65
|
+
}, 0);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
error: (err) => {
|
|
69
|
+
this.progress = 0;
|
|
70
|
+
this.message =
|
|
71
|
+
err.error?.message || "Đã xảy ra lỗi trong quá trình upload file!";
|
|
72
|
+
this.currentFile = undefined;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// keep last value until next open; we'll clear right before opening dialog
|
|
76
|
+
}
|
|
77
|
+
triggerFileInput() {
|
|
78
|
+
// clear value so selecting the same file triggers change event
|
|
79
|
+
this.fileInputRef.nativeElement.value = null;
|
|
80
|
+
this.fileInputRef.nativeElement.click();
|
|
81
|
+
}
|
|
82
|
+
onChooseClick(event) {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
event.stopPropagation();
|
|
85
|
+
this.triggerFileInput();
|
|
86
|
+
}
|
|
87
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ExcelUploadComponent, deps: [{ token: i1.FileService }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
88
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ExcelUploadComponent, isStandalone: true, selector: "app-excel-upload", outputs: { onChange: "onChange" }, providers: [
|
|
89
|
+
{
|
|
90
|
+
provide: NG_VALUE_ACCESSOR,
|
|
91
|
+
useExisting: ExcelUploadComponent,
|
|
92
|
+
multi: true
|
|
93
|
+
}
|
|
94
|
+
], viewQueries: [{ propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true }], ngImport: i0, template: "<div class=\"excel-upload-container\">\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n title=\"Click \u0111\u1EC3 t\u1EA3i file l\u00EAn\"\r\n #fileInput\r\n id=\"uploadFile\"\r\n (change)=\"handleInputChange($event)\"\r\n name=\"uploadFile\"\r\n accept=\".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel\"\r\n />\r\n\r\n <!-- PrimeNG File Upload UI -->\r\n <div class=\"upload-area\" (click)=\"triggerFileInput()\">\r\n <div class=\"upload-icon\">\r\n <i class=\"pi pi-file-excel\"></i>\r\n </div>\r\n\r\n <div class=\"upload-text\">\r\n <p class=\"file-name\">\r\n {{ fileAttr || \"Ch\u1ECDn file Excel \u0111\u1EC3 upload\" }}\r\n </p>\r\n <p class=\"file-info\">H\u1ED7 tr\u1EE3 \u0111\u1ECBnh d\u1EA1ng: .xlsx, .xls, .csv</p>\r\n </div>\r\n\r\n <p-button\r\n label=\"Ch\u1ECDn file\"\r\n icon=\"pi pi-upload\"\r\n (onClick)=\"onChooseClick($event)\"\r\n styleClass=\"p-button-outlined\"\r\n >\r\n </p-button>\r\n </div>\r\n\r\n <!-- Progress Bar -->\r\n <div *ngIf=\"progress > 0\" class=\"progress-container\">\r\n <p-progressBar [value]=\"progress\" styleClass=\"w-full h-2\"> </p-progressBar>\r\n <p class=\"progress-text\">\u0110ang upload: {{ progress }}%</p>\r\n </div>\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"message\" class=\"error-container\">\r\n <div class=\"error-content\">\r\n <i class=\"pi pi-exclamation-triangle\"></i>\r\n <span>{{ message }}</span>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".excel-upload-container{width:100%}.excel-upload-container .upload-area{border:2px dashed #d1d5db;border-radius:.5rem;padding:1.5rem;text-align:center;transition:all .3s ease;cursor:pointer}.excel-upload-container .upload-area:hover{border-color:#3b82f6;background-color:#f8fafc}.excel-upload-container .upload-area.drag-over{border-color:#3b82f6;background-color:#eff6ff}.excel-upload-container .upload-icon{width:4rem;height:4rem;background-color:#eff6ff;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 1rem}.excel-upload-container .upload-icon i{font-size:1.5rem;color:#3b82f6}.excel-upload-container .upload-text{margin-bottom:1rem}.excel-upload-container .upload-text .file-name{font-size:1.125rem;font-weight:500;color:#374151;margin-bottom:.25rem}.excel-upload-container .upload-text .file-info{font-size:.875rem;color:#6b7280}.excel-upload-container .progress-container{margin-top:1rem}.excel-upload-container .progress-container .progress-text{font-size:.875rem;color:#6b7280;text-align:center;margin-top:.5rem}.excel-upload-container .error-container{margin-top:1rem;padding:.75rem;background-color:#fef2f2;border:1px solid #fecaca;border-radius:.5rem}.excel-upload-container .error-container .error-content{display:flex;align-items:center}.excel-upload-container .error-container .error-content i{color:#dc2626;margin-right:.5rem}.excel-upload-container .error-container .error-content span{font-size:.875rem;color:#991b1b}#uploadFile{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i4.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }, { kind: "ngmodule", type: InputTextModule }] }); }
|
|
95
|
+
}
|
|
96
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ExcelUploadComponent, decorators: [{
|
|
97
|
+
type: Component,
|
|
98
|
+
args: [{ selector: "app-excel-upload", standalone: true, providers: [
|
|
99
|
+
{
|
|
100
|
+
provide: NG_VALUE_ACCESSOR,
|
|
101
|
+
useExisting: ExcelUploadComponent,
|
|
102
|
+
multi: true
|
|
103
|
+
}
|
|
104
|
+
], imports: [
|
|
105
|
+
CommonModule,
|
|
106
|
+
FormsModule,
|
|
107
|
+
ButtonModule,
|
|
108
|
+
ProgressBarModule,
|
|
109
|
+
InputTextModule
|
|
110
|
+
], template: "<div class=\"excel-upload-container\">\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n title=\"Click \u0111\u1EC3 t\u1EA3i file l\u00EAn\"\r\n #fileInput\r\n id=\"uploadFile\"\r\n (change)=\"handleInputChange($event)\"\r\n name=\"uploadFile\"\r\n accept=\".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel\"\r\n />\r\n\r\n <!-- PrimeNG File Upload UI -->\r\n <div class=\"upload-area\" (click)=\"triggerFileInput()\">\r\n <div class=\"upload-icon\">\r\n <i class=\"pi pi-file-excel\"></i>\r\n </div>\r\n\r\n <div class=\"upload-text\">\r\n <p class=\"file-name\">\r\n {{ fileAttr || \"Ch\u1ECDn file Excel \u0111\u1EC3 upload\" }}\r\n </p>\r\n <p class=\"file-info\">H\u1ED7 tr\u1EE3 \u0111\u1ECBnh d\u1EA1ng: .xlsx, .xls, .csv</p>\r\n </div>\r\n\r\n <p-button\r\n label=\"Ch\u1ECDn file\"\r\n icon=\"pi pi-upload\"\r\n (onClick)=\"onChooseClick($event)\"\r\n styleClass=\"p-button-outlined\"\r\n >\r\n </p-button>\r\n </div>\r\n\r\n <!-- Progress Bar -->\r\n <div *ngIf=\"progress > 0\" class=\"progress-container\">\r\n <p-progressBar [value]=\"progress\" styleClass=\"w-full h-2\"> </p-progressBar>\r\n <p class=\"progress-text\">\u0110ang upload: {{ progress }}%</p>\r\n </div>\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"message\" class=\"error-container\">\r\n <div class=\"error-content\">\r\n <i class=\"pi pi-exclamation-triangle\"></i>\r\n <span>{{ message }}</span>\r\n </div>\r\n </div>\r\n</div>\r\n", styles: [".excel-upload-container{width:100%}.excel-upload-container .upload-area{border:2px dashed #d1d5db;border-radius:.5rem;padding:1.5rem;text-align:center;transition:all .3s ease;cursor:pointer}.excel-upload-container .upload-area:hover{border-color:#3b82f6;background-color:#f8fafc}.excel-upload-container .upload-area.drag-over{border-color:#3b82f6;background-color:#eff6ff}.excel-upload-container .upload-icon{width:4rem;height:4rem;background-color:#eff6ff;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 1rem}.excel-upload-container .upload-icon i{font-size:1.5rem;color:#3b82f6}.excel-upload-container .upload-text{margin-bottom:1rem}.excel-upload-container .upload-text .file-name{font-size:1.125rem;font-weight:500;color:#374151;margin-bottom:.25rem}.excel-upload-container .upload-text .file-info{font-size:.875rem;color:#6b7280}.excel-upload-container .progress-container{margin-top:1rem}.excel-upload-container .progress-container .progress-text{font-size:.875rem;color:#6b7280;text-align:center;margin-top:.5rem}.excel-upload-container .error-container{margin-top:1rem;padding:.75rem;background-color:#fef2f2;border:1px solid #fecaca;border-radius:.5rem}.excel-upload-container .error-container .error-content{display:flex;align-items:center}.excel-upload-container .error-container .error-content i{color:#dc2626;margin-right:.5rem}.excel-upload-container .error-container .error-content span{font-size:.875rem;color:#991b1b}#uploadFile{display:none}\n"] }]
|
|
111
|
+
}], ctorParameters: () => [{ type: i1.FileService }, { type: i0.ChangeDetectorRef }, { type: i0.NgZone }], propDecorators: { fileInputRef: [{
|
|
112
|
+
type: ViewChild,
|
|
113
|
+
args: ["fileInput", { static: false }]
|
|
114
|
+
}], onChange: [{
|
|
115
|
+
type: Output
|
|
116
|
+
}] } });
|
|
117
|
+
|
|
118
|
+
class MediaUploadComponent {
|
|
119
|
+
constructor() {
|
|
120
|
+
this.fileWap_GCSService = inject(FileWAPGCSService);
|
|
121
|
+
this.messageService = inject(MessageService);
|
|
122
|
+
this.kind = "image";
|
|
123
|
+
this.label = "";
|
|
124
|
+
this.placeholder = "";
|
|
125
|
+
this.accept = null; // override default
|
|
126
|
+
this.maxSizeMb = null; // override default
|
|
127
|
+
this.initialUrl = null; // can be absolute or storage path
|
|
128
|
+
// When uploading succeeds, emit file_path (storage path) and absolute preview url if available
|
|
129
|
+
this.uploaded = new EventEmitter();
|
|
130
|
+
this.removed = new EventEmitter();
|
|
131
|
+
// Expose state outward if parent wants to block submit while uploading
|
|
132
|
+
this.uploadingChange = new EventEmitter();
|
|
133
|
+
// Internal states
|
|
134
|
+
this.progress = 0;
|
|
135
|
+
this.previewUrl = null;
|
|
136
|
+
this.storagePath = null;
|
|
137
|
+
this.fileSize = 0;
|
|
138
|
+
this.fileName = "";
|
|
139
|
+
this.videoDurationText = "";
|
|
140
|
+
}
|
|
141
|
+
ngOnInit() {
|
|
142
|
+
if (this.initialUrl) {
|
|
143
|
+
this.setPreviewFromPath(this.initialUrl);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
ngOnChanges(changes) {
|
|
147
|
+
if (changes["initialUrl"]) {
|
|
148
|
+
const value = changes["initialUrl"].currentValue;
|
|
149
|
+
if (value) {
|
|
150
|
+
this.setPreviewFromPath(value);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// cleared
|
|
154
|
+
this.previewUrl = null;
|
|
155
|
+
this.storagePath = null;
|
|
156
|
+
this.fileName = "";
|
|
157
|
+
this.fileSize = 0;
|
|
158
|
+
this.videoDurationText = "";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
ngOnDestroy() { }
|
|
163
|
+
get acceptMime() {
|
|
164
|
+
if (this.accept)
|
|
165
|
+
return this.accept;
|
|
166
|
+
if (this.kind === "image")
|
|
167
|
+
return "image/*";
|
|
168
|
+
if (this.kind === "video")
|
|
169
|
+
return "video/*";
|
|
170
|
+
return ".pdf,.doc,.docx";
|
|
171
|
+
}
|
|
172
|
+
get maxBytes() {
|
|
173
|
+
const override = this.maxSizeMb;
|
|
174
|
+
const sizeMb = override ??
|
|
175
|
+
(this.kind === "video" ? 100 : this.kind === "image" ? 10 : 30);
|
|
176
|
+
return sizeMb * 1024 * 1024;
|
|
177
|
+
}
|
|
178
|
+
onSelect(event) {
|
|
179
|
+
const file = event.files?.[0];
|
|
180
|
+
if (!file)
|
|
181
|
+
return;
|
|
182
|
+
// type validation
|
|
183
|
+
if (this.kind === "image" && !file.type.startsWith("image/")) {
|
|
184
|
+
this.error("Vui lòng chọn file ảnh hợp lệ (PNG, JPG, GIF)");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (this.kind === "video" && !file.type.startsWith("video/")) {
|
|
188
|
+
this.error("Vui lòng chọn file video hợp lệ");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (this.kind === "file") {
|
|
192
|
+
const allowed = [
|
|
193
|
+
"application/pdf",
|
|
194
|
+
"application/msword",
|
|
195
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
196
|
+
];
|
|
197
|
+
if (!allowed.includes(file.type)) {
|
|
198
|
+
this.error("Vui lòng chọn file PDF, DOC hoặc DOCX");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// size validation
|
|
203
|
+
if (file.size > this.maxBytes) {
|
|
204
|
+
const sizeMb = Math.round(this.maxBytes / (1024 * 1024));
|
|
205
|
+
this.error(`Kích thước file quá lớn (Tối đa ${sizeMb}MB)`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
this.fileSize = file.size;
|
|
209
|
+
this.fileName = file.name;
|
|
210
|
+
this.upload(file);
|
|
211
|
+
}
|
|
212
|
+
remove() {
|
|
213
|
+
this.progress = 0;
|
|
214
|
+
this.previewUrl = null;
|
|
215
|
+
this.storagePath = null;
|
|
216
|
+
this.fileSize = 0;
|
|
217
|
+
this.fileName = "";
|
|
218
|
+
this.videoDurationText = "";
|
|
219
|
+
this.removed.emit();
|
|
220
|
+
}
|
|
221
|
+
upload(file) {
|
|
222
|
+
this.progress = 0;
|
|
223
|
+
this.uploadingChange.emit(true);
|
|
224
|
+
this.fileWap_GCSService.attachFile(file).subscribe({
|
|
225
|
+
next: (event) => {
|
|
226
|
+
if (event.type === HttpEventType.UploadProgress) {
|
|
227
|
+
this.progress = Math.round((100 * event.loaded) / (event.total || 1));
|
|
228
|
+
if (this.progress === 100)
|
|
229
|
+
this.progress = 80; // waiting server finalize
|
|
230
|
+
}
|
|
231
|
+
else if (event instanceof HttpResponse) {
|
|
232
|
+
const filePath = event?.body?.data?.file_path;
|
|
233
|
+
if (filePath) {
|
|
234
|
+
this.progress = 100;
|
|
235
|
+
this.storagePath = filePath;
|
|
236
|
+
const abs = event?.body?.data?.absolute_url ||
|
|
237
|
+
event?.body?.data?.presigned_url;
|
|
238
|
+
if (abs) {
|
|
239
|
+
this.previewUrl = abs;
|
|
240
|
+
if (this.kind === "video")
|
|
241
|
+
this.loadVideoDuration(abs);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
this.setPreviewFromPath(filePath);
|
|
245
|
+
}
|
|
246
|
+
this.uploaded.emit({
|
|
247
|
+
filePath,
|
|
248
|
+
absoluteUrl: this.previewUrl || undefined,
|
|
249
|
+
duration: this.kind === "video"
|
|
250
|
+
? this.getVideoDurationFromUrl(this.previewUrl)
|
|
251
|
+
: undefined,
|
|
252
|
+
size: this.fileSize || undefined
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
this.uploadingChange.emit(false);
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
error: () => {
|
|
259
|
+
this.progress = 0;
|
|
260
|
+
this.previewUrl = null;
|
|
261
|
+
this.storagePath = null;
|
|
262
|
+
this.uploadingChange.emit(false);
|
|
263
|
+
this.error(this.kind === "image"
|
|
264
|
+
? "Tải ảnh thất bại"
|
|
265
|
+
: this.kind === "video"
|
|
266
|
+
? "Tải video thất bại"
|
|
267
|
+
: "Tải file thất bại");
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
setPreviewFromPath(path) {
|
|
272
|
+
if (!path)
|
|
273
|
+
return;
|
|
274
|
+
if (path.startsWith("http")) {
|
|
275
|
+
this.previewUrl = path;
|
|
276
|
+
if (this.kind === "video")
|
|
277
|
+
this.loadVideoDuration(path);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
this.fileWap_GCSService.getSignedLink(path).subscribe((res) => {
|
|
281
|
+
const abs = res?.data?.absolute_url ||
|
|
282
|
+
res?.data?.presigned_url ||
|
|
283
|
+
res?.absolute_url ||
|
|
284
|
+
res?.presigned_url;
|
|
285
|
+
if (abs) {
|
|
286
|
+
this.previewUrl = abs;
|
|
287
|
+
if (this.kind === "video")
|
|
288
|
+
this.loadVideoDuration(abs);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
loadVideoDuration(url) {
|
|
293
|
+
const video = document.createElement("video");
|
|
294
|
+
video.preload = "metadata";
|
|
295
|
+
video.onloadedmetadata = () => {
|
|
296
|
+
const total = video.duration || 0;
|
|
297
|
+
const m = Math.floor(total / 60);
|
|
298
|
+
const s = Math.floor(total % 60)
|
|
299
|
+
.toString()
|
|
300
|
+
.padStart(2, "0");
|
|
301
|
+
this.videoDurationText = `${m}:${s}`;
|
|
302
|
+
};
|
|
303
|
+
video.src = url;
|
|
304
|
+
}
|
|
305
|
+
getVideoDurationFromUrl(url) {
|
|
306
|
+
if (!url || this.kind !== "video")
|
|
307
|
+
return 0;
|
|
308
|
+
// This is a simplified approach - in practice you might want to cache duration
|
|
309
|
+
return 0; // Duration will be set from initialData in editor
|
|
310
|
+
}
|
|
311
|
+
error(detail) {
|
|
312
|
+
this.messageService.add({ severity: "error", summary: "Lỗi", detail });
|
|
313
|
+
}
|
|
314
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MediaUploadComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
315
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: MediaUploadComponent, isStandalone: true, selector: "app-media-upload", inputs: { kind: "kind", label: "label", placeholder: "placeholder", accept: "accept", maxSizeMb: "maxSizeMb", initialUrl: "initialUrl" }, outputs: { uploaded: "uploaded", removed: "removed", uploadingChange: "uploadingChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"mb-4\">\r\n <label class=\"mb-3 block font-bold\">{{ label }}</label>\r\n\r\n <div\r\n class=\"rounded-lg border-2 border-dashed border-gray-300 p-4 text-center transition-colors hover:border-blue-400\"\r\n >\r\n <p-fileUpload\r\n mode=\"basic\"\r\n [accept]=\"acceptMime\"\r\n (onSelect)=\"onSelect($event)\"\r\n [chooseLabel]=\"\r\n placeholder ||\r\n (kind === 'image'\r\n ? 'Ch\u1ECDn \u1EA3nh'\r\n : kind === 'video'\r\n ? 'Upload Video'\r\n : 'Upload File')\r\n \"\r\n styleClass=\"p-button-outlined\"\r\n />\r\n\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'image'\">\r\n Supported: JPG, PNG, GIF (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'file'\">\r\n Supported: PDF, DOC, DOCX (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'video'\">\r\n Supported: MP4, AVI, MOV (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n\r\n <!-- IMAGE PREVIEW -->\r\n <div *ngIf=\"kind === 'image' && previewUrl\" class=\"relative mt-4\">\r\n <div class=\"rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <img\r\n [src]=\"previewUrl\"\r\n alt=\"Preview\"\r\n class=\"h-48 w-full rounded-lg bg-gray-50 object-contain\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <p class=\"mt-1 text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n K\u00EDch th\u01B0\u1EDBc: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n </div>\r\n\r\n <!-- FILE PREVIEW -->\r\n <div *ngIf=\"kind === 'file' && (previewUrl || storagePath)\" class=\"mt-4\">\r\n <div class=\"relative rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <div class=\"flex h-24 items-center justify-center\">\r\n <i class=\"pi pi-file text-4xl text-gray-400\"></i>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <div class=\"mt-2\">\r\n <div class=\"text-sm font-medium\">{{ fileName || \"File \u0111\u00E3 t\u1EA3i\" }}</div>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n K\u00EDch th\u01B0\u1EDBc: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- VIDEO PREVIEW -->\r\n <div *ngIf=\"kind === 'video' && previewUrl\" class=\"mt-4\">\r\n <div class=\"relative rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <video\r\n [src]=\"previewUrl\"\r\n controls\r\n preload=\"metadata\"\r\n class=\"h-48 w-full rounded-lg bg-gray-50 object-contain\"\r\n ></video>\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <div class=\"mt-2\">\r\n <div class=\"text-sm font-medium\">{{ fileName || \"Video \u0111\u00E3 t\u1EA3i\" }}</div>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n K\u00EDch th\u01B0\u1EDBc: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"videoDurationText\">\r\n Th\u1EDDi l\u01B0\u1EE3ng: {{ videoDurationText }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- PROGRESS -->\r\n <div *ngIf=\"progress > 0 && progress < 100\" class=\"mt-4\">\r\n <p-progressBar [value]=\"progress\"></p-progressBar>\r\n <div class=\"mt-1 text-sm text-gray-600\">\r\n \u0110ang t\u1EA3i file... {{ progress }}%\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.DecimalPipe, name: "number" }, { kind: "ngmodule", type: FileUploadModule }, { kind: "component", type: i2.FileUpload, selector: "p-fileupload, p-fileUpload", inputs: ["name", "url", "method", "multiple", "accept", "disabled", "auto", "withCredentials", "maxFileSize", "invalidFileSizeMessageSummary", "invalidFileSizeMessageDetail", "invalidFileTypeMessageSummary", "invalidFileTypeMessageDetail", "invalidFileLimitMessageDetail", "invalidFileLimitMessageSummary", "style", "styleClass", "previewWidth", "chooseLabel", "uploadLabel", "cancelLabel", "chooseIcon", "uploadIcon", "cancelIcon", "showUploadButton", "showCancelButton", "mode", "headers", "customUpload", "fileLimit", "uploadStyleClass", "cancelStyleClass", "removeStyleClass", "chooseStyleClass", "chooseButtonProps", "uploadButtonProps", "cancelButtonProps", "files"], outputs: ["onBeforeUpload", "onSend", "onUpload", "onError", "onClear", "onRemove", "onSelect", "onProgress", "uploadHandler", "onImageError", "onRemoveUploadedFile"] }, { kind: "ngmodule", type: ProgressBarModule }, { kind: "component", type: i4.ProgressBar, selector: "p-progressBar, p-progressbar, p-progress-bar", inputs: ["value", "showValue", "styleClass", "valueStyleClass", "style", "unit", "mode", "color"] }] }); }
|
|
316
|
+
}
|
|
317
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: MediaUploadComponent, decorators: [{
|
|
318
|
+
type: Component,
|
|
319
|
+
args: [{ selector: "app-media-upload", standalone: true, imports: [CommonModule, FileUploadModule, ProgressBarModule], template: "<div class=\"mb-4\">\r\n <label class=\"mb-3 block font-bold\">{{ label }}</label>\r\n\r\n <div\r\n class=\"rounded-lg border-2 border-dashed border-gray-300 p-4 text-center transition-colors hover:border-blue-400\"\r\n >\r\n <p-fileUpload\r\n mode=\"basic\"\r\n [accept]=\"acceptMime\"\r\n (onSelect)=\"onSelect($event)\"\r\n [chooseLabel]=\"\r\n placeholder ||\r\n (kind === 'image'\r\n ? 'Ch\u1ECDn \u1EA3nh'\r\n : kind === 'video'\r\n ? 'Upload Video'\r\n : 'Upload File')\r\n \"\r\n styleClass=\"p-button-outlined\"\r\n />\r\n\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'image'\">\r\n Supported: JPG, PNG, GIF (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'file'\">\r\n Supported: PDF, DOC, DOCX (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'video'\">\r\n Supported: MP4, AVI, MOV (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n\r\n <!-- IMAGE PREVIEW -->\r\n <div *ngIf=\"kind === 'image' && previewUrl\" class=\"relative mt-4\">\r\n <div class=\"rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <img\r\n [src]=\"previewUrl\"\r\n alt=\"Preview\"\r\n class=\"h-48 w-full rounded-lg bg-gray-50 object-contain\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <p class=\"mt-1 text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n K\u00EDch th\u01B0\u1EDBc: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n </div>\r\n\r\n <!-- FILE PREVIEW -->\r\n <div *ngIf=\"kind === 'file' && (previewUrl || storagePath)\" class=\"mt-4\">\r\n <div class=\"relative rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <div class=\"flex h-24 items-center justify-center\">\r\n <i class=\"pi pi-file text-4xl text-gray-400\"></i>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <div class=\"mt-2\">\r\n <div class=\"text-sm font-medium\">{{ fileName || \"File \u0111\u00E3 t\u1EA3i\" }}</div>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n K\u00EDch th\u01B0\u1EDBc: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- VIDEO PREVIEW -->\r\n <div *ngIf=\"kind === 'video' && previewUrl\" class=\"mt-4\">\r\n <div class=\"relative rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <video\r\n [src]=\"previewUrl\"\r\n controls\r\n preload=\"metadata\"\r\n class=\"h-48 w-full rounded-lg bg-gray-50 object-contain\"\r\n ></video>\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <div class=\"mt-2\">\r\n <div class=\"text-sm font-medium\">{{ fileName || \"Video \u0111\u00E3 t\u1EA3i\" }}</div>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n K\u00EDch th\u01B0\u1EDBc: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"videoDurationText\">\r\n Th\u1EDDi l\u01B0\u1EE3ng: {{ videoDurationText }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- PROGRESS -->\r\n <div *ngIf=\"progress > 0 && progress < 100\" class=\"mt-4\">\r\n <p-progressBar [value]=\"progress\"></p-progressBar>\r\n <div class=\"mt-1 text-sm text-gray-600\">\r\n \u0110ang t\u1EA3i file... {{ progress }}%\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n" }]
|
|
320
|
+
}], propDecorators: { kind: [{
|
|
321
|
+
type: Input
|
|
322
|
+
}], label: [{
|
|
323
|
+
type: Input
|
|
324
|
+
}], placeholder: [{
|
|
325
|
+
type: Input
|
|
326
|
+
}], accept: [{
|
|
327
|
+
type: Input
|
|
328
|
+
}], maxSizeMb: [{
|
|
329
|
+
type: Input
|
|
330
|
+
}], initialUrl: [{
|
|
331
|
+
type: Input
|
|
332
|
+
}], uploaded: [{
|
|
333
|
+
type: Output
|
|
334
|
+
}], removed: [{
|
|
335
|
+
type: Output
|
|
336
|
+
}], uploadingChange: [{
|
|
337
|
+
type: Output
|
|
338
|
+
}] } });
|
|
339
|
+
|
|
340
|
+
class TreeSelectComponent {
|
|
341
|
+
constructor() {
|
|
342
|
+
this.controlName = "";
|
|
343
|
+
this.dataSource = [];
|
|
344
|
+
this.onChange = new EventEmitter();
|
|
345
|
+
this.destroy$ = new Subject();
|
|
346
|
+
// Signals
|
|
347
|
+
this.selectedNodes = signal([]);
|
|
348
|
+
this.treeNodes = signal([]);
|
|
349
|
+
}
|
|
350
|
+
ngOnInit() {
|
|
351
|
+
// Watch for dataSource changes
|
|
352
|
+
if (this.form && this.controlName) {
|
|
353
|
+
const control = this.form.get(this.controlName);
|
|
354
|
+
if (control) {
|
|
355
|
+
this.selectedNodes.set(control.value || []);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
ngOnChanges(changes) {
|
|
360
|
+
// Watch for dataSource changes
|
|
361
|
+
if (changes["dataSource"] && this.dataSource) {
|
|
362
|
+
this.treeNodes.set(this.dataSource);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
ngOnDestroy() {
|
|
366
|
+
this.destroy$.next();
|
|
367
|
+
this.destroy$.complete();
|
|
368
|
+
}
|
|
369
|
+
onSelectionChange(event) {
|
|
370
|
+
const selectedIds = this.extractStoreIds(event || []);
|
|
371
|
+
this.selectedNodes.set(event || []);
|
|
372
|
+
// Update form control if available
|
|
373
|
+
if (this.form && this.controlName) {
|
|
374
|
+
this.form.patchValue({ [this.controlName]: selectedIds });
|
|
375
|
+
}
|
|
376
|
+
// Emit change event
|
|
377
|
+
this.onChange.emit({ value: selectedIds, selectedNodes: event });
|
|
378
|
+
}
|
|
379
|
+
extractStoreIds(selectedNodes) {
|
|
380
|
+
const storeIds = [];
|
|
381
|
+
const extractFromNodes = (nodes) => {
|
|
382
|
+
nodes.forEach(node => {
|
|
383
|
+
if (node.data?.type === "store" && node.data.storeId) {
|
|
384
|
+
// Add individual store
|
|
385
|
+
storeIds.push(node.data.storeId);
|
|
386
|
+
}
|
|
387
|
+
else if (node.data?.type === "area" && node.children) {
|
|
388
|
+
// If area is selected, add all its stores
|
|
389
|
+
extractFromNodes(node.children);
|
|
390
|
+
}
|
|
391
|
+
else if (node.children) {
|
|
392
|
+
// Recursively check children
|
|
393
|
+
extractFromNodes(node.children);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
extractFromNodes(selectedNodes);
|
|
398
|
+
return storeIds;
|
|
399
|
+
}
|
|
400
|
+
// Public methods
|
|
401
|
+
getSelectedStoreIds() {
|
|
402
|
+
return this.extractStoreIds(this.selectedNodes());
|
|
403
|
+
}
|
|
404
|
+
clearSelection() {
|
|
405
|
+
this.selectedNodes.set([]);
|
|
406
|
+
if (this.form && this.controlName) {
|
|
407
|
+
this.form.patchValue({ [this.controlName]: [] });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TreeSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
411
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: TreeSelectComponent, isStandalone: true, selector: "tree-select", inputs: { form: "form", controlName: "controlName", dataSource: "dataSource" }, outputs: { onChange: "onChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"mb-2\">\r\n <p-treeSelect\r\n [options]=\"treeNodes()\"\r\n [ngModel]=\"selectedNodes()\"\r\n (ngModelChange)=\"onSelectionChange($event)\"\r\n placeholder=\"Ch\u1ECDn c\u1EEDa h\u00E0ng\"\r\n [selectionMode]=\"'checkbox'\"\r\n [metaKeySelection]=\"false\"\r\n [filter]=\"true\"\r\n [display]=\"'chip'\"\r\n selectedItemsLabel=\"{0} c\u1EEDa h\u00E0ng \u0111\u00E3 ch\u1ECDn\"\r\n [appendTo]=\"null\"\r\n (onSelectionChange)=\"onSelectionChange($event)\"\r\n panelStyleClass=\"!w-auto !max-w-full min-w-0\"\r\n styleClass=\"!w-full\"\r\n >\r\n <ng-template pTemplate=\"item\" let-node>\r\n <span class=\"whitespace-nowrap\" [title]=\"node.label\">\r\n {{ node.label }}\r\n </span>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"empty\">\r\n <div class=\"flex flex-col items-center justify-center px-4 py-8\">\r\n <i class=\"pi pi-inbox mb-3 text-4xl text-gray-400\"></i>\r\n <p class=\"font-medium text-gray-500\">Kh\u00F4ng t\u00ECm th\u1EA5y k\u1EBFt qu\u1EA3</p>\r\n <p class=\"mt-1 text-gray-400\">Th\u1EED t\u1EEB kh\u00F3a kh\u00E1c</p>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"emptyfilter\">\r\n <div class=\"flex flex-col items-center justify-center px-4 py-6\">\r\n <i class=\"pi pi-search mb-2 text-3xl text-gray-400\"></i>\r\n <p class=\"font-medium text-gray-500\">Kh\u00F4ng t\u00ECm th\u1EA5y k\u1EBFt qu\u1EA3</p>\r\n <p class=\"mt-1 text-gray-400\">Th\u1EED t\u1EEB kh\u00F3a kh\u00E1c</p>\r\n </div>\r\n </ng-template>\r\n </p-treeSelect>\r\n</div>\r\n", styles: ["::ng-deep .p-treeselect{width:100%!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: TreeSelectModule }, { kind: "component", type: i2$1.TreeSelect, selector: "p-treeSelect, p-treeselect, p-tree-select", inputs: ["inputId", "scrollHeight", "disabled", "metaKeySelection", "variant", "display", "selectionMode", "tabindex", "ariaLabel", "ariaLabelledBy", "placeholder", "panelClass", "panelStyle", "fluid", "panelStyleClass", "containerStyle", "containerStyleClass", "labelStyle", "labelStyleClass", "overlayOptions", "emptyMessage", "appendTo", "filter", "filterBy", "filterMode", "filterPlaceholder", "filterLocale", "filterInputAutoFocus", "propagateSelectionDown", "propagateSelectionUp", "showClear", "resetFilterOnHide", "virtualScroll", "virtualScrollItemSize", "size", "virtualScrollOptions", "autofocus", "options", "showTransitionOptions", "hideTransitionOptions", "loading"], outputs: ["onNodeExpand", "onNodeCollapse", "onShow", "onHide", "onClear", "onFilter", "onFocus", "onBlur", "onNodeUnselect", "onNodeSelect"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }] }); }
|
|
412
|
+
}
|
|
413
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: TreeSelectComponent, decorators: [{
|
|
414
|
+
type: Component,
|
|
415
|
+
args: [{ selector: "tree-select", standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule, TreeSelectModule], template: "<div class=\"mb-2\">\r\n <p-treeSelect\r\n [options]=\"treeNodes()\"\r\n [ngModel]=\"selectedNodes()\"\r\n (ngModelChange)=\"onSelectionChange($event)\"\r\n placeholder=\"Ch\u1ECDn c\u1EEDa h\u00E0ng\"\r\n [selectionMode]=\"'checkbox'\"\r\n [metaKeySelection]=\"false\"\r\n [filter]=\"true\"\r\n [display]=\"'chip'\"\r\n selectedItemsLabel=\"{0} c\u1EEDa h\u00E0ng \u0111\u00E3 ch\u1ECDn\"\r\n [appendTo]=\"null\"\r\n (onSelectionChange)=\"onSelectionChange($event)\"\r\n panelStyleClass=\"!w-auto !max-w-full min-w-0\"\r\n styleClass=\"!w-full\"\r\n >\r\n <ng-template pTemplate=\"item\" let-node>\r\n <span class=\"whitespace-nowrap\" [title]=\"node.label\">\r\n {{ node.label }}\r\n </span>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"empty\">\r\n <div class=\"flex flex-col items-center justify-center px-4 py-8\">\r\n <i class=\"pi pi-inbox mb-3 text-4xl text-gray-400\"></i>\r\n <p class=\"font-medium text-gray-500\">Kh\u00F4ng t\u00ECm th\u1EA5y k\u1EBFt qu\u1EA3</p>\r\n <p class=\"mt-1 text-gray-400\">Th\u1EED t\u1EEB kh\u00F3a kh\u00E1c</p>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"emptyfilter\">\r\n <div class=\"flex flex-col items-center justify-center px-4 py-6\">\r\n <i class=\"pi pi-search mb-2 text-3xl text-gray-400\"></i>\r\n <p class=\"font-medium text-gray-500\">Kh\u00F4ng t\u00ECm th\u1EA5y k\u1EBFt qu\u1EA3</p>\r\n <p class=\"mt-1 text-gray-400\">Th\u1EED t\u1EEB kh\u00F3a kh\u00E1c</p>\r\n </div>\r\n </ng-template>\r\n </p-treeSelect>\r\n</div>\r\n", styles: ["::ng-deep .p-treeselect{width:100%!important}\n"] }]
|
|
416
|
+
}], propDecorators: { form: [{
|
|
417
|
+
type: Input
|
|
418
|
+
}], controlName: [{
|
|
419
|
+
type: Input
|
|
420
|
+
}], dataSource: [{
|
|
421
|
+
type: Input
|
|
422
|
+
}], onChange: [{
|
|
423
|
+
type: Output
|
|
424
|
+
}] } });
|
|
425
|
+
|
|
426
|
+
class ReLoginDialogComponent {
|
|
427
|
+
constructor(ref) {
|
|
428
|
+
this.ref = ref;
|
|
429
|
+
}
|
|
430
|
+
onConfirm() {
|
|
431
|
+
this.ref.close({ confirmed: true });
|
|
432
|
+
}
|
|
433
|
+
onCancel() {
|
|
434
|
+
this.ref.close({ confirmed: false });
|
|
435
|
+
}
|
|
436
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ReLoginDialogComponent, deps: [{ token: i1$3.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
437
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: ReLoginDialogComponent, isStandalone: true, selector: "app-relogin-dialog", ngImport: i0, template: "<div class=\"dialog-wrapper\">\r\n <div class=\"dialog-illustration\">\r\n <i class=\"pi pi-exclamation-triangle dialog-icon\" aria-hidden=\"true\"></i>\r\n </div>\r\n <div class=\"dialog-content\">\r\n <h2 class=\"dialog-title\">Phi\u00EAn \u0111\u0103ng nh\u1EADp \u0111\u00E3 h\u1EBFt h\u1EA1n</h2>\r\n <p class=\"dialog-text\">\r\n Vui l\u00F2ng \u0111\u0103ng nh\u1EADp l\u1EA1i \u0111\u1EC3 ti\u1EBFp t\u1EE5c s\u1EED d\u1EE5ng h\u1EC7 th\u1ED1ng.\r\n </p>\r\n </div>\r\n <div class=\"dialog-actions\">\r\n <button\r\n pButton\r\n type=\"button\"\r\n label=\"\u0110\u00F3ng\"\r\n [severity]=\"'secondary'\"\r\n class=\"p-button-text\"\r\n (click)=\"onCancel()\"\r\n ></button>\r\n <button\r\n pButton\r\n type=\"button\"\r\n label=\"\u0110\u0103ng nh\u1EADp l\u1EA1i\"\r\n [severity]=\"'primary'\"\r\n icon=\"pi pi-sign-in\"\r\n (click)=\"onConfirm()\"\r\n ></button>\r\n </div>\r\n</div>\r\n", styles: [".dialog-wrapper{display:flex;flex-direction:column;align-items:center;text-align:center;padding:1rem 1.25rem;min-width:320px;max-width:460px;animation:fadeInUp .18s ease-out}.dialog-illustration{margin:.25rem 0 1rem}.dialog-illustration .dialog-icon{font-size:2.75rem;color:var(--yellow-500, #f59e0b);text-shadow:0 2px 8px rgba(0,0,0,.06);animation:floatY 1.4s ease-in-out infinite,glowPulse 1.8s ease-in-out infinite;will-change:transform,filter}.dialog-title{margin:.25rem 0;font-size:1.25rem;font-weight:700}.dialog-text{margin:0 0 1rem;color:var(--text-secondary-color, #6b7280)}.dialog-actions{display:flex;gap:.5rem}@keyframes fadeInUp{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes floatY{0%,to{transform:translateY(0) scale(1)}50%{transform:translateY(-6px) scale(1.03)}}@keyframes glowPulse{0%,to{filter:drop-shadow(0 2px 6px rgba(245,158,11,.25))}50%{filter:drop-shadow(0 6px 14px rgba(245,158,11,.45))}}:host ::ng-deep .p-dynamic-dialog .p-dialog-content{padding:0}:host ::ng-deep .relogin-dynamic-dialog .p-dialog-header{font-weight:600}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }] }); }
|
|
438
|
+
}
|
|
439
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ReLoginDialogComponent, decorators: [{
|
|
440
|
+
type: Component,
|
|
441
|
+
args: [{ selector: "app-relogin-dialog", standalone: true, imports: [CommonModule, ButtonModule], template: "<div class=\"dialog-wrapper\">\r\n <div class=\"dialog-illustration\">\r\n <i class=\"pi pi-exclamation-triangle dialog-icon\" aria-hidden=\"true\"></i>\r\n </div>\r\n <div class=\"dialog-content\">\r\n <h2 class=\"dialog-title\">Phi\u00EAn \u0111\u0103ng nh\u1EADp \u0111\u00E3 h\u1EBFt h\u1EA1n</h2>\r\n <p class=\"dialog-text\">\r\n Vui l\u00F2ng \u0111\u0103ng nh\u1EADp l\u1EA1i \u0111\u1EC3 ti\u1EBFp t\u1EE5c s\u1EED d\u1EE5ng h\u1EC7 th\u1ED1ng.\r\n </p>\r\n </div>\r\n <div class=\"dialog-actions\">\r\n <button\r\n pButton\r\n type=\"button\"\r\n label=\"\u0110\u00F3ng\"\r\n [severity]=\"'secondary'\"\r\n class=\"p-button-text\"\r\n (click)=\"onCancel()\"\r\n ></button>\r\n <button\r\n pButton\r\n type=\"button\"\r\n label=\"\u0110\u0103ng nh\u1EADp l\u1EA1i\"\r\n [severity]=\"'primary'\"\r\n icon=\"pi pi-sign-in\"\r\n (click)=\"onConfirm()\"\r\n ></button>\r\n </div>\r\n</div>\r\n", styles: [".dialog-wrapper{display:flex;flex-direction:column;align-items:center;text-align:center;padding:1rem 1.25rem;min-width:320px;max-width:460px;animation:fadeInUp .18s ease-out}.dialog-illustration{margin:.25rem 0 1rem}.dialog-illustration .dialog-icon{font-size:2.75rem;color:var(--yellow-500, #f59e0b);text-shadow:0 2px 8px rgba(0,0,0,.06);animation:floatY 1.4s ease-in-out infinite,glowPulse 1.8s ease-in-out infinite;will-change:transform,filter}.dialog-title{margin:.25rem 0;font-size:1.25rem;font-weight:700}.dialog-text{margin:0 0 1rem;color:var(--text-secondary-color, #6b7280)}.dialog-actions{display:flex;gap:.5rem}@keyframes fadeInUp{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes floatY{0%,to{transform:translateY(0) scale(1)}50%{transform:translateY(-6px) scale(1.03)}}@keyframes glowPulse{0%,to{filter:drop-shadow(0 2px 6px rgba(245,158,11,.25))}50%{filter:drop-shadow(0 6px 14px rgba(245,158,11,.45))}}:host ::ng-deep .p-dynamic-dialog .p-dialog-content{padding:0}:host ::ng-deep .relogin-dynamic-dialog .p-dialog-header{font-weight:600}\n"] }]
|
|
442
|
+
}], ctorParameters: () => [{ type: i1$3.DynamicDialogRef }] });
|
|
443
|
+
|
|
444
|
+
class AuthDialogService {
|
|
445
|
+
constructor() {
|
|
446
|
+
this.dialogService = inject(DialogService);
|
|
447
|
+
this.activeRef = null;
|
|
448
|
+
}
|
|
449
|
+
openReLoginDialog() {
|
|
450
|
+
if (this.activeRef) {
|
|
451
|
+
// If dialog already open, just return a pending promise that resolves when it closes
|
|
452
|
+
return new Promise(resolve => {
|
|
453
|
+
this.activeRef?.onClose.subscribe((result) => {
|
|
454
|
+
resolve(!!result?.confirmed);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
const config = {
|
|
459
|
+
header: "Xác thực lại",
|
|
460
|
+
width: "420px",
|
|
461
|
+
modal: true,
|
|
462
|
+
closable: false,
|
|
463
|
+
dismissableMask: false,
|
|
464
|
+
contentStyle: { overflow: "hidden" },
|
|
465
|
+
styleClass: "relogin-dynamic-dialog"
|
|
466
|
+
};
|
|
467
|
+
this.activeRef = this.dialogService.open(ReLoginDialogComponent, config);
|
|
468
|
+
return new Promise(resolve => {
|
|
469
|
+
this.activeRef?.onClose.subscribe((result) => {
|
|
470
|
+
this.activeRef = null;
|
|
471
|
+
resolve(!!result?.confirmed);
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AuthDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
476
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AuthDialogService, providedIn: "root" }); }
|
|
477
|
+
}
|
|
478
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AuthDialogService, decorators: [{
|
|
479
|
+
type: Injectable,
|
|
480
|
+
args: [{ providedIn: "root" }]
|
|
481
|
+
}] });
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Subscription sink that holds Observable subscriptions
|
|
485
|
+
* until you call unsubscribe on it in ngOnDestroy.
|
|
486
|
+
*/
|
|
487
|
+
class SubSink {
|
|
488
|
+
constructor() {
|
|
489
|
+
this._subs = [];
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Subscription sink that holds Observable subscriptions
|
|
493
|
+
* until you call unsubscribe on it in ngOnDestroy.
|
|
494
|
+
*
|
|
495
|
+
* @example
|
|
496
|
+
* In Angular:
|
|
497
|
+
* ```
|
|
498
|
+
* private subs = new SubSink();
|
|
499
|
+
* ...
|
|
500
|
+
* this.subs.sink = observable$.subscribe(
|
|
501
|
+
* this.subs.add(observable$.subscribe(...));
|
|
502
|
+
* ...
|
|
503
|
+
* ngOnDestroy() {
|
|
504
|
+
* this.subs.unsubscribe();
|
|
505
|
+
* }
|
|
506
|
+
* ```
|
|
507
|
+
*/
|
|
508
|
+
/**
|
|
509
|
+
* Add subscriptions to the tracked subscriptions
|
|
510
|
+
* @example
|
|
511
|
+
* this.subs.add(observable$.subscribe(...));
|
|
512
|
+
*/
|
|
513
|
+
add(...subscriptions) {
|
|
514
|
+
this._subs = this._subs.concat(subscriptions);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Assign subscription to this sink to add it to the tracked subscriptions
|
|
518
|
+
* @example
|
|
519
|
+
* this.subs.sink = observable$.subscribe(...);
|
|
520
|
+
*/
|
|
521
|
+
set sink(subscription) {
|
|
522
|
+
this._subs.push(subscription);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Unsubscribe to all subscriptions in ngOnDestroy()
|
|
526
|
+
* @example
|
|
527
|
+
* ngOnDestroy() {
|
|
528
|
+
* this.subs.unsubscribe();
|
|
529
|
+
* }
|
|
530
|
+
*/
|
|
531
|
+
unsubscribe() {
|
|
532
|
+
this._subs.forEach(sub => sub && sub.unsubscribe());
|
|
533
|
+
this._subs = [];
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* A class that automatically unsubscribes all observables when the object gets destroyed
|
|
539
|
+
*/
|
|
540
|
+
class UnsubscribeOnDestroyAdapter {
|
|
541
|
+
constructor() {
|
|
542
|
+
/**
|
|
543
|
+
* The subscription sink object that stores all subscriptions
|
|
544
|
+
*/
|
|
545
|
+
this.subs = new SubSink();
|
|
546
|
+
this.destroy$ = new Subject();
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* The lifecycle hook that unsubscribes all subscriptions when the component / object gets destroyed
|
|
550
|
+
*/
|
|
551
|
+
ngOnDestroy() {
|
|
552
|
+
this.subs?.unsubscribe();
|
|
553
|
+
this.destroy$.next();
|
|
554
|
+
this.destroy$.complete();
|
|
555
|
+
}
|
|
556
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UnsubscribeOnDestroyAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
557
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UnsubscribeOnDestroyAdapter }); }
|
|
558
|
+
}
|
|
559
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: UnsubscribeOnDestroyAdapter, decorators: [{
|
|
560
|
+
type: Injectable
|
|
561
|
+
}] });
|
|
562
|
+
|
|
563
|
+
function withBaseStore(initialState) {
|
|
564
|
+
return signalStoreFeature(withState(initialState), withComputed(store => ({
|
|
565
|
+
keys: computed(() => Object.keys(store.data?.() ?? {})),
|
|
566
|
+
getLoading: computed(() => (key) => !!store.loading?.()?.[key]),
|
|
567
|
+
getError: computed(() => (key) => store.error?.()?.[key] ?? null),
|
|
568
|
+
getData: computed(() => (key) => store.data?.()?.[key] ?? null),
|
|
569
|
+
getCustom: computed(() => store.custom?.())
|
|
570
|
+
})), withMethods(store => ({
|
|
571
|
+
setData: (key, data) => {
|
|
572
|
+
patchState(store, {
|
|
573
|
+
data: { ...(store.data?.() ?? {}), [key]: data }
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
setLoading: (key, loading) => {
|
|
577
|
+
patchState(store, {
|
|
578
|
+
loading: { ...(store.loading?.() ?? {}), [key]: loading }
|
|
579
|
+
});
|
|
580
|
+
},
|
|
581
|
+
setError: (key, error) => {
|
|
582
|
+
patchState(store, {
|
|
583
|
+
error: { ...(store.error?.() ?? {}), [key]: error }
|
|
584
|
+
});
|
|
585
|
+
},
|
|
586
|
+
clearKey: (key) => {
|
|
587
|
+
const currentLoading = store.loading?.() ?? {};
|
|
588
|
+
const currentError = store.error?.() ?? {};
|
|
589
|
+
const currentData = store.data?.() ?? {};
|
|
590
|
+
const { [key]: _l, ...restLoading } = currentLoading;
|
|
591
|
+
const { [key]: _e, ...restError } = currentError;
|
|
592
|
+
const { [key]: _d, ...restData } = currentData;
|
|
593
|
+
patchState(store, {
|
|
594
|
+
loading: restLoading,
|
|
595
|
+
error: restError,
|
|
596
|
+
data: restData
|
|
597
|
+
});
|
|
598
|
+
},
|
|
599
|
+
clearAll: () => {
|
|
600
|
+
patchState(store, { ...initialState });
|
|
601
|
+
}
|
|
602
|
+
})));
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/*
|
|
606
|
+
* Public API Surface of view/shared
|
|
607
|
+
*/
|
|
608
|
+
// Components
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Generated bundle index. Do not edit.
|
|
612
|
+
*/
|
|
613
|
+
|
|
614
|
+
export { AuthDialogService, ExcelUploadComponent, MediaUploadComponent, ReLoginDialogComponent, SubSink, TreeSelectComponent, UnsubscribeOnDestroyAdapter, withBaseStore };
|
|
615
|
+
//# sourceMappingURL=web-portal-view-shared.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-portal-view-shared.mjs","sources":["../../../../projects/view/shared/src/lib/components/excel-upload/excel-upload.component.ts","../../../../projects/view/shared/src/lib/components/excel-upload/excel-upload.component.html","../../../../projects/view/shared/src/lib/components/media-upload/media-upload.component.ts","../../../../projects/view/shared/src/lib/components/media-upload/media-upload.component.html","../../../../projects/view/shared/src/lib/components/tree-select/tree-select.component.ts","../../../../projects/view/shared/src/lib/components/tree-select/tree-select.component.html","../../../../projects/view/shared/src/lib/components/relogin-dialog/relogin-dialog.component.ts","../../../../projects/view/shared/src/lib/components/relogin-dialog/relogin-dialog.component.html","../../../../projects/view/shared/src/lib/services/auth/auth-dialog.service.ts","../../../../projects/view/shared/src/lib/sub-sink.ts","../../../../projects/view/shared/src/lib/UnsubscribeOnDestroyAdapter.ts","../../../../projects/view/shared/src/lib/base.store.ts","../../../../projects/view/shared/src/public-api.ts","../../../../projects/view/shared/src/web-portal-view-shared.ts"],"sourcesContent":["import { HttpEventType, HttpResponse } from \"@angular/common/http\";\r\nimport {\r\n Component,\r\n EventEmitter,\r\n OnInit,\r\n Output,\r\n ViewChild\r\n} from \"@angular/core\";\r\nimport { ChangeDetectorRef, NgZone } from \"@angular/core\";\r\nimport { FormsModule, NG_VALUE_ACCESSOR } from \"@angular/forms\";\r\nimport { FileService } from \"@web-portal/core-infrastructure\";\r\nimport { CommonModule } from \"@angular/common\";\r\nimport { ButtonModule } from \"primeng/button\";\r\nimport { ProgressBarModule } from \"primeng/progressbar\";\r\nimport { InputTextModule } from \"primeng/inputtext\";\r\n@Component({\r\n selector: \"app-excel-upload\",\r\n styleUrls: [\"./excel-upload.component.scss\"],\r\n templateUrl: \"./excel-upload.component.html\",\r\n standalone: true,\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n useExisting: ExcelUploadComponent,\r\n multi: true\r\n }\r\n ],\r\n imports: [\r\n CommonModule,\r\n FormsModule,\r\n ButtonModule,\r\n ProgressBarModule,\r\n InputTextModule\r\n ]\r\n})\r\nexport class ExcelUploadComponent implements OnInit {\r\n @ViewChild(\"fileInput\", { static: false }) fileInputRef!: any;\r\n\r\n ngOnInit(): void {}\r\n selectedFiles?: FileList;\r\n currentFile?: File;\r\n progress = 0;\r\n message = \"\";\r\n filePath = \"\";\r\n absoluteURL = \"\";\r\n @Output() onChange: EventEmitter<any> = new EventEmitter<any>();\r\n fileAttr = \"Chọn file tải lên\";\r\n\r\n constructor(\r\n private fileService: FileService,\r\n private cdr: ChangeDetectorRef,\r\n private ngZone: NgZone\r\n ) {}\r\n\r\n handleInputChange(event: any): void {\r\n const file: File | null = event.target.files?.[0];\r\n if (!file) {\r\n return;\r\n }\r\n\r\n this.selectedFiles = null;\r\n this.progress = 0;\r\n this.currentFile = file;\r\n this.fileAttr = file?.name;\r\n\r\n this.fileService.uploadExcel(this.currentFile).subscribe({\r\n next: (value: any) => {\r\n if (value.type === HttpEventType.UploadProgress) {\r\n // update progress on next tick to avoid ExpressionChanged errors\r\n const percent = Math.round((100 * value.loaded) / value.total!);\r\n setTimeout(() => {\r\n this.ngZone.run(() => {\r\n this.progress = percent;\r\n this.cdr.detectChanges();\r\n });\r\n }, 0);\r\n } else if (value instanceof HttpResponse) {\r\n // emit and ensure view is stable before next change detection\r\n this.onChange.emit(value.body.data);\r\n setTimeout(() => {\r\n this.ngZone.run(() => this.cdr.detectChanges());\r\n }, 0);\r\n }\r\n },\r\n error: (err: any) => {\r\n this.progress = 0;\r\n this.message =\r\n err.error?.message || \"Đã xảy ra lỗi trong quá trình upload file!\";\r\n this.currentFile = undefined;\r\n }\r\n });\r\n // keep last value until next open; we'll clear right before opening dialog\r\n }\r\n\r\n triggerFileInput(): void {\r\n // clear value so selecting the same file triggers change event\r\n this.fileInputRef.nativeElement.value = null;\r\n this.fileInputRef.nativeElement.click();\r\n }\r\n\r\n onChooseClick(event: Event): void {\r\n event.preventDefault();\r\n event.stopPropagation();\r\n this.triggerFileInput();\r\n }\r\n}\r\n","<div class=\"excel-upload-container\">\r\n <!-- Hidden file input -->\r\n <input\r\n type=\"file\"\r\n title=\"Click để tải file lên\"\r\n #fileInput\r\n id=\"uploadFile\"\r\n (change)=\"handleInputChange($event)\"\r\n name=\"uploadFile\"\r\n accept=\".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel\"\r\n />\r\n\r\n <!-- PrimeNG File Upload UI -->\r\n <div class=\"upload-area\" (click)=\"triggerFileInput()\">\r\n <div class=\"upload-icon\">\r\n <i class=\"pi pi-file-excel\"></i>\r\n </div>\r\n\r\n <div class=\"upload-text\">\r\n <p class=\"file-name\">\r\n {{ fileAttr || \"Chọn file Excel để upload\" }}\r\n </p>\r\n <p class=\"file-info\">Hỗ trợ định dạng: .xlsx, .xls, .csv</p>\r\n </div>\r\n\r\n <p-button\r\n label=\"Chọn file\"\r\n icon=\"pi pi-upload\"\r\n (onClick)=\"onChooseClick($event)\"\r\n styleClass=\"p-button-outlined\"\r\n >\r\n </p-button>\r\n </div>\r\n\r\n <!-- Progress Bar -->\r\n <div *ngIf=\"progress > 0\" class=\"progress-container\">\r\n <p-progressBar [value]=\"progress\" styleClass=\"w-full h-2\"> </p-progressBar>\r\n <p class=\"progress-text\">Đang upload: {{ progress }}%</p>\r\n </div>\r\n\r\n <!-- Error Message -->\r\n <div *ngIf=\"message\" class=\"error-container\">\r\n <div class=\"error-content\">\r\n <i class=\"pi pi-exclamation-triangle\"></i>\r\n <span>{{ message }}</span>\r\n </div>\r\n </div>\r\n</div>\r\n","import { CommonModule } from \"@angular/common\";\r\nimport {\r\n Component,\r\n EventEmitter,\r\n Input,\r\n OnChanges,\r\n OnDestroy,\r\n OnInit,\r\n Output,\r\n SimpleChanges,\r\n inject\r\n} from \"@angular/core\";\r\nimport { FileUploadModule } from \"primeng/fileupload\";\r\nimport { ProgressBarModule } from \"primeng/progressbar\";\r\nimport { MessageService } from \"primeng/api\";\r\nimport { HttpEventType, HttpResponse } from \"@angular/common/http\";\r\nimport { FileWAPGCSService } from \"@web-portal/core-infrastructure\";\r\n\r\ntype MediaKind = \"image\" | \"file\" | \"video\";\r\n\r\n@Component({\r\n selector: \"app-media-upload\",\r\n standalone: true,\r\n imports: [CommonModule, FileUploadModule, ProgressBarModule],\r\n templateUrl: \"./media-upload.component.html\"\r\n})\r\nexport class MediaUploadComponent implements OnInit, OnChanges, OnDestroy {\r\n private readonly fileWap_GCSService = inject(FileWAPGCSService);\r\n private readonly messageService = inject(MessageService);\r\n\r\n @Input() kind: MediaKind = \"image\";\r\n @Input() label: string = \"\";\r\n @Input() placeholder: string = \"\";\r\n @Input() accept: string | null = null; // override default\r\n @Input() maxSizeMb: number | null = null; // override default\r\n @Input() initialUrl: string | null = null; // can be absolute or storage path\r\n\r\n // When uploading succeeds, emit file_path (storage path) and absolute preview url if available\r\n @Output() uploaded = new EventEmitter<{\r\n filePath: string;\r\n absoluteUrl?: string;\r\n duration?: number;\r\n size?: number;\r\n }>();\r\n @Output() removed = new EventEmitter<void>();\r\n\r\n // Expose state outward if parent wants to block submit while uploading\r\n @Output() uploadingChange = new EventEmitter<boolean>();\r\n\r\n // Internal states\r\n progress: number = 0;\r\n previewUrl: string | null = null;\r\n storagePath: string | null = null;\r\n fileSize: number = 0;\r\n fileName: string = \"\";\r\n videoDurationText: string = \"\";\r\n\r\n ngOnInit(): void {\r\n if (this.initialUrl) {\r\n this.setPreviewFromPath(this.initialUrl);\r\n }\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges): void {\r\n if (changes[\"initialUrl\"]) {\r\n const value = changes[\"initialUrl\"].currentValue as string | null;\r\n if (value) {\r\n this.setPreviewFromPath(value);\r\n } else {\r\n // cleared\r\n this.previewUrl = null;\r\n this.storagePath = null;\r\n this.fileName = \"\";\r\n this.fileSize = 0;\r\n this.videoDurationText = \"\";\r\n }\r\n }\r\n }\r\n\r\n ngOnDestroy(): void {}\r\n\r\n get acceptMime(): string {\r\n if (this.accept) return this.accept;\r\n if (this.kind === \"image\") return \"image/*\";\r\n if (this.kind === \"video\") return \"video/*\";\r\n return \".pdf,.doc,.docx\";\r\n }\r\n\r\n get maxBytes(): number {\r\n const override = this.maxSizeMb;\r\n const sizeMb =\r\n override ??\r\n (this.kind === \"video\" ? 100 : this.kind === \"image\" ? 10 : 30);\r\n return sizeMb * 1024 * 1024;\r\n }\r\n\r\n onSelect(event: any): void {\r\n const file = event.files?.[0];\r\n if (!file) return;\r\n\r\n // type validation\r\n if (this.kind === \"image\" && !file.type.startsWith(\"image/\")) {\r\n this.error(\"Vui lòng chọn file ảnh hợp lệ (PNG, JPG, GIF)\");\r\n return;\r\n }\r\n if (this.kind === \"video\" && !file.type.startsWith(\"video/\")) {\r\n this.error(\"Vui lòng chọn file video hợp lệ\");\r\n return;\r\n }\r\n if (this.kind === \"file\") {\r\n const allowed = [\r\n \"application/pdf\",\r\n \"application/msword\",\r\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"\r\n ];\r\n if (!allowed.includes(file.type)) {\r\n this.error(\"Vui lòng chọn file PDF, DOC hoặc DOCX\");\r\n return;\r\n }\r\n }\r\n\r\n // size validation\r\n if (file.size > this.maxBytes) {\r\n const sizeMb = Math.round(this.maxBytes / (1024 * 1024));\r\n this.error(`Kích thước file quá lớn (Tối đa ${sizeMb}MB)`);\r\n return;\r\n }\r\n\r\n this.fileSize = file.size;\r\n this.fileName = file.name;\r\n this.upload(file);\r\n }\r\n\r\n remove(): void {\r\n this.progress = 0;\r\n this.previewUrl = null;\r\n this.storagePath = null;\r\n this.fileSize = 0;\r\n this.fileName = \"\";\r\n this.videoDurationText = \"\";\r\n this.removed.emit();\r\n }\r\n\r\n private upload(file: File): void {\r\n this.progress = 0;\r\n this.uploadingChange.emit(true);\r\n\r\n this.fileWap_GCSService.attachFile(file).subscribe({\r\n next: (event: any) => {\r\n if (event.type === HttpEventType.UploadProgress) {\r\n this.progress = Math.round((100 * event.loaded) / (event.total || 1));\r\n if (this.progress === 100) this.progress = 80; // waiting server finalize\r\n } else if (event instanceof HttpResponse) {\r\n const filePath = event?.body?.data?.file_path;\r\n if (filePath) {\r\n this.progress = 100;\r\n this.storagePath = filePath;\r\n const abs =\r\n event?.body?.data?.absolute_url ||\r\n event?.body?.data?.presigned_url;\r\n if (abs) {\r\n this.previewUrl = abs;\r\n if (this.kind === \"video\") this.loadVideoDuration(abs);\r\n } else {\r\n this.setPreviewFromPath(filePath);\r\n }\r\n this.uploaded.emit({\r\n filePath,\r\n absoluteUrl: this.previewUrl || undefined,\r\n duration:\r\n this.kind === \"video\"\r\n ? this.getVideoDurationFromUrl(this.previewUrl)\r\n : undefined,\r\n size: this.fileSize || undefined\r\n });\r\n }\r\n this.uploadingChange.emit(false);\r\n }\r\n },\r\n error: () => {\r\n this.progress = 0;\r\n this.previewUrl = null;\r\n this.storagePath = null;\r\n this.uploadingChange.emit(false);\r\n this.error(\r\n this.kind === \"image\"\r\n ? \"Tải ảnh thất bại\"\r\n : this.kind === \"video\"\r\n ? \"Tải video thất bại\"\r\n : \"Tải file thất bại\"\r\n );\r\n }\r\n });\r\n }\r\n\r\n private setPreviewFromPath(path: string): void {\r\n if (!path) return;\r\n if (path.startsWith(\"http\")) {\r\n this.previewUrl = path;\r\n if (this.kind === \"video\") this.loadVideoDuration(path);\r\n return;\r\n }\r\n this.fileWap_GCSService.getSignedLink(path).subscribe((res: any) => {\r\n const abs =\r\n res?.data?.absolute_url ||\r\n res?.data?.presigned_url ||\r\n res?.absolute_url ||\r\n res?.presigned_url;\r\n if (abs) {\r\n this.previewUrl = abs;\r\n if (this.kind === \"video\") this.loadVideoDuration(abs);\r\n }\r\n });\r\n }\r\n\r\n private loadVideoDuration(url: string): void {\r\n const video = document.createElement(\"video\");\r\n video.preload = \"metadata\";\r\n video.onloadedmetadata = () => {\r\n const total = video.duration || 0;\r\n const m = Math.floor(total / 60);\r\n const s = Math.floor(total % 60)\r\n .toString()\r\n .padStart(2, \"0\");\r\n this.videoDurationText = `${m}:${s}`;\r\n };\r\n video.src = url;\r\n }\r\n\r\n private getVideoDurationFromUrl(url: string | null): number {\r\n if (!url || this.kind !== \"video\") return 0;\r\n // This is a simplified approach - in practice you might want to cache duration\r\n return 0; // Duration will be set from initialData in editor\r\n }\r\n\r\n private error(detail: string): void {\r\n this.messageService.add({ severity: \"error\", summary: \"Lỗi\", detail });\r\n }\r\n}\r\n","<div class=\"mb-4\">\r\n <label class=\"mb-3 block font-bold\">{{ label }}</label>\r\n\r\n <div\r\n class=\"rounded-lg border-2 border-dashed border-gray-300 p-4 text-center transition-colors hover:border-blue-400\"\r\n >\r\n <p-fileUpload\r\n mode=\"basic\"\r\n [accept]=\"acceptMime\"\r\n (onSelect)=\"onSelect($event)\"\r\n [chooseLabel]=\"\r\n placeholder ||\r\n (kind === 'image'\r\n ? 'Chọn ảnh'\r\n : kind === 'video'\r\n ? 'Upload Video'\r\n : 'Upload File')\r\n \"\r\n styleClass=\"p-button-outlined\"\r\n />\r\n\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'image'\">\r\n Supported: JPG, PNG, GIF (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'file'\">\r\n Supported: PDF, DOC, DOCX (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n <p class=\"mt-2 text-sm text-gray-500\" *ngIf=\"kind === 'video'\">\r\n Supported: MP4, AVI, MOV (Max\r\n {{ maxBytes / (1024 * 1024) | number: \"1.0-0\" }}MB)\r\n </p>\r\n\r\n <!-- IMAGE PREVIEW -->\r\n <div *ngIf=\"kind === 'image' && previewUrl\" class=\"relative mt-4\">\r\n <div class=\"rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <img\r\n [src]=\"previewUrl\"\r\n alt=\"Preview\"\r\n class=\"h-48 w-full rounded-lg bg-gray-50 object-contain\"\r\n />\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <p class=\"mt-1 text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n Kích thước: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n </div>\r\n\r\n <!-- FILE PREVIEW -->\r\n <div *ngIf=\"kind === 'file' && (previewUrl || storagePath)\" class=\"mt-4\">\r\n <div class=\"relative rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <div class=\"flex h-24 items-center justify-center\">\r\n <i class=\"pi pi-file text-4xl text-gray-400\"></i>\r\n </div>\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <div class=\"mt-2\">\r\n <div class=\"text-sm font-medium\">{{ fileName || \"File đã tải\" }}</div>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n Kích thước: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- VIDEO PREVIEW -->\r\n <div *ngIf=\"kind === 'video' && previewUrl\" class=\"mt-4\">\r\n <div class=\"relative rounded-lg border border-gray-300 bg-gray-50 p-4\">\r\n <video\r\n [src]=\"previewUrl\"\r\n controls\r\n preload=\"metadata\"\r\n class=\"h-48 w-full rounded-lg bg-gray-50 object-contain\"\r\n ></video>\r\n <button\r\n type=\"button\"\r\n class=\"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-red-500 text-sm text-white hover:bg-red-600\"\r\n (click)=\"remove()\"\r\n >\r\n <i class=\"pi pi-times\"></i>\r\n </button>\r\n </div>\r\n <div class=\"mt-2\">\r\n <div class=\"text-sm font-medium\">{{ fileName || \"Video đã tải\" }}</div>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"fileSize\">\r\n Kích thước: {{ fileSize / 1024 / 1024 | number: \"1.2-2\" }} MB\r\n </p>\r\n <p class=\"text-sm text-gray-500\" *ngIf=\"videoDurationText\">\r\n Thời lượng: {{ videoDurationText }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- PROGRESS -->\r\n <div *ngIf=\"progress > 0 && progress < 100\" class=\"mt-4\">\r\n <p-progressBar [value]=\"progress\"></p-progressBar>\r\n <div class=\"mt-1 text-sm text-gray-600\">\r\n Đang tải file... {{ progress }}%\r\n </div>\r\n </div>\r\n </div>\r\n</div>\r\n","import {\r\n Component,\r\n Input,\r\n Output,\r\n EventEmitter,\r\n OnInit,\r\n OnDestroy,\r\n OnChanges,\r\n SimpleChanges,\r\n signal\r\n} from \"@angular/core\";\r\nimport { CommonModule } from \"@angular/common\";\r\nimport { FormsModule, ReactiveFormsModule } from \"@angular/forms\";\r\nimport { TreeSelectModule } from \"primeng/treeselect\";\r\nimport { TreeNode } from \"./tree-select.interface\";\r\nimport { Subject } from \"rxjs\";\r\n\r\n@Component({\r\n selector: \"tree-select\",\r\n standalone: true,\r\n imports: [CommonModule, FormsModule, ReactiveFormsModule, TreeSelectModule],\r\n templateUrl: \"./tree-select.component.html\",\r\n styleUrls: [\"./tree-select.component.scss\"]\r\n})\r\nexport class TreeSelectComponent implements OnInit, OnDestroy, OnChanges {\r\n @Input() form: any;\r\n @Input() controlName: string = \"\";\r\n @Input() dataSource: TreeNode[] = [];\r\n\r\n @Output() onChange = new EventEmitter<any>();\r\n\r\n private readonly destroy$ = new Subject<void>();\r\n\r\n // Signals\r\n readonly selectedNodes = signal<any[]>([]);\r\n readonly treeNodes = signal<TreeNode[]>([]);\r\n\r\n ngOnInit() {\r\n // Watch for dataSource changes\r\n if (this.form && this.controlName) {\r\n const control = this.form.get(this.controlName);\r\n if (control) {\r\n this.selectedNodes.set(control.value || []);\r\n }\r\n }\r\n }\r\n\r\n ngOnChanges(changes: SimpleChanges) {\r\n // Watch for dataSource changes\r\n if (changes[\"dataSource\"] && this.dataSource) {\r\n this.treeNodes.set(this.dataSource);\r\n }\r\n }\r\n\r\n ngOnDestroy() {\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n\r\n onSelectionChange(event: any): void {\r\n const selectedIds = this.extractStoreIds(event || []);\r\n this.selectedNodes.set(event || []);\r\n\r\n // Update form control if available\r\n if (this.form && this.controlName) {\r\n this.form.patchValue({ [this.controlName]: selectedIds });\r\n }\r\n\r\n // Emit change event\r\n this.onChange.emit({ value: selectedIds, selectedNodes: event });\r\n }\r\n\r\n private extractStoreIds(selectedNodes: TreeNode[]): number[] {\r\n const storeIds: number[] = [];\r\n\r\n const extractFromNodes = (nodes: TreeNode[]) => {\r\n nodes.forEach(node => {\r\n if (node.data?.type === \"store\" && node.data.storeId) {\r\n // Add individual store\r\n storeIds.push(node.data.storeId);\r\n } else if (node.data?.type === \"area\" && node.children) {\r\n // If area is selected, add all its stores\r\n extractFromNodes(node.children);\r\n } else if (node.children) {\r\n // Recursively check children\r\n extractFromNodes(node.children);\r\n }\r\n });\r\n };\r\n\r\n extractFromNodes(selectedNodes);\r\n return storeIds;\r\n }\r\n\r\n // Public methods\r\n public getSelectedStoreIds(): number[] {\r\n return this.extractStoreIds(this.selectedNodes());\r\n }\r\n\r\n public clearSelection(): void {\r\n this.selectedNodes.set([]);\r\n if (this.form && this.controlName) {\r\n this.form.patchValue({ [this.controlName]: [] });\r\n }\r\n }\r\n}\r\n","<div class=\"mb-2\">\r\n <p-treeSelect\r\n [options]=\"treeNodes()\"\r\n [ngModel]=\"selectedNodes()\"\r\n (ngModelChange)=\"onSelectionChange($event)\"\r\n placeholder=\"Chọn cửa hàng\"\r\n [selectionMode]=\"'checkbox'\"\r\n [metaKeySelection]=\"false\"\r\n [filter]=\"true\"\r\n [display]=\"'chip'\"\r\n selectedItemsLabel=\"{0} cửa hàng đã chọn\"\r\n [appendTo]=\"null\"\r\n (onSelectionChange)=\"onSelectionChange($event)\"\r\n panelStyleClass=\"!w-auto !max-w-full min-w-0\"\r\n styleClass=\"!w-full\"\r\n >\r\n <ng-template pTemplate=\"item\" let-node>\r\n <span class=\"whitespace-nowrap\" [title]=\"node.label\">\r\n {{ node.label }}\r\n </span>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"empty\">\r\n <div class=\"flex flex-col items-center justify-center px-4 py-8\">\r\n <i class=\"pi pi-inbox mb-3 text-4xl text-gray-400\"></i>\r\n <p class=\"font-medium text-gray-500\">Không tìm thấy kết quả</p>\r\n <p class=\"mt-1 text-gray-400\">Thử từ khóa khác</p>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template pTemplate=\"emptyfilter\">\r\n <div class=\"flex flex-col items-center justify-center px-4 py-6\">\r\n <i class=\"pi pi-search mb-2 text-3xl text-gray-400\"></i>\r\n <p class=\"font-medium text-gray-500\">Không tìm thấy kết quả</p>\r\n <p class=\"mt-1 text-gray-400\">Thử từ khóa khác</p>\r\n </div>\r\n </ng-template>\r\n </p-treeSelect>\r\n</div>\r\n","import { Component } from \"@angular/core\";\r\nimport { DynamicDialogRef } from \"primeng/dynamicdialog\";\r\nimport { CommonModule } from \"@angular/common\";\r\nimport { ButtonModule } from \"primeng/button\";\r\n\r\n@Component({\r\n selector: \"app-relogin-dialog\",\r\n standalone: true,\r\n imports: [CommonModule, ButtonModule],\r\n templateUrl: \"./relogin-dialog.component.html\",\r\n styleUrls: [\"./relogin-dialog.component.scss\"]\r\n})\r\nexport class ReLoginDialogComponent {\r\n constructor(private ref: DynamicDialogRef) {}\r\n\r\n onConfirm(): void {\r\n this.ref.close({ confirmed: true });\r\n }\r\n\r\n onCancel(): void {\r\n this.ref.close({ confirmed: false });\r\n }\r\n}\r\n","<div class=\"dialog-wrapper\">\r\n <div class=\"dialog-illustration\">\r\n <i class=\"pi pi-exclamation-triangle dialog-icon\" aria-hidden=\"true\"></i>\r\n </div>\r\n <div class=\"dialog-content\">\r\n <h2 class=\"dialog-title\">Phiên đăng nhập đã hết hạn</h2>\r\n <p class=\"dialog-text\">\r\n Vui lòng đăng nhập lại để tiếp tục sử dụng hệ thống.\r\n </p>\r\n </div>\r\n <div class=\"dialog-actions\">\r\n <button\r\n pButton\r\n type=\"button\"\r\n label=\"Đóng\"\r\n [severity]=\"'secondary'\"\r\n class=\"p-button-text\"\r\n (click)=\"onCancel()\"\r\n ></button>\r\n <button\r\n pButton\r\n type=\"button\"\r\n label=\"Đăng nhập lại\"\r\n [severity]=\"'primary'\"\r\n icon=\"pi pi-sign-in\"\r\n (click)=\"onConfirm()\"\r\n ></button>\r\n </div>\r\n</div>\r\n","import { Injectable, inject } from \"@angular/core\";\r\nimport {\r\n DialogService,\r\n DynamicDialogRef,\r\n DynamicDialogConfig\r\n} from \"primeng/dynamicdialog\";\r\nimport { ReLoginDialogComponent } from \"../../components/relogin-dialog/relogin-dialog.component\";\r\n\r\nimport { IAuthDialogService } from \"@web-portal/core-infrastructure\";\r\n\r\n@Injectable({ providedIn: \"root\" })\r\nexport class AuthDialogService implements IAuthDialogService {\r\n private dialogService = inject(DialogService);\r\n private activeRef: DynamicDialogRef | null = null;\r\n\r\n openReLoginDialog(): Promise<boolean> {\r\n if (this.activeRef) {\r\n // If dialog already open, just return a pending promise that resolves when it closes\r\n return new Promise(resolve => {\r\n this.activeRef?.onClose.subscribe((result: any) => {\r\n resolve(!!result?.confirmed);\r\n });\r\n });\r\n }\r\n\r\n const config: DynamicDialogConfig = {\r\n header: \"Xác thực lại\",\r\n width: \"420px\",\r\n modal: true,\r\n closable: false,\r\n dismissableMask: false,\r\n contentStyle: { overflow: \"hidden\" },\r\n styleClass: \"relogin-dynamic-dialog\"\r\n };\r\n\r\n this.activeRef = this.dialogService.open(ReLoginDialogComponent, config);\r\n\r\n return new Promise(resolve => {\r\n this.activeRef?.onClose.subscribe((result: any) => {\r\n this.activeRef = null;\r\n resolve(!!result?.confirmed);\r\n });\r\n });\r\n }\r\n}\r\n","import { SubscriptionLike } from \"rxjs\";\r\n\r\n/**\r\n * Subscription sink that holds Observable subscriptions\r\n * until you call unsubscribe on it in ngOnDestroy.\r\n */\r\nexport class SubSink {\r\n protected _subs: SubscriptionLike[] = [];\r\n\r\n /**\r\n * Subscription sink that holds Observable subscriptions\r\n * until you call unsubscribe on it in ngOnDestroy.\r\n *\r\n * @example\r\n * In Angular:\r\n * ```\r\n * private subs = new SubSink();\r\n * ...\r\n * this.subs.sink = observable$.subscribe(\r\n * this.subs.add(observable$.subscribe(...));\r\n * ...\r\n * ngOnDestroy() {\r\n * this.subs.unsubscribe();\r\n * }\r\n * ```\r\n */\r\n\r\n /**\r\n * Add subscriptions to the tracked subscriptions\r\n * @example\r\n * this.subs.add(observable$.subscribe(...));\r\n */\r\n add(...subscriptions: SubscriptionLike[]) {\r\n this._subs = this._subs.concat(subscriptions);\r\n }\r\n\r\n /**\r\n * Assign subscription to this sink to add it to the tracked subscriptions\r\n * @example\r\n * this.subs.sink = observable$.subscribe(...);\r\n */\r\n set sink(subscription: SubscriptionLike) {\r\n this._subs.push(subscription);\r\n }\r\n\r\n /**\r\n * Unsubscribe to all subscriptions in ngOnDestroy()\r\n * @example\r\n * ngOnDestroy() {\r\n * this.subs.unsubscribe();\r\n * }\r\n */\r\n unsubscribe() {\r\n this._subs.forEach(sub => sub && sub.unsubscribe());\r\n this._subs = [];\r\n }\r\n}\r\n","import { AfterViewInit, Injectable, OnDestroy } from \"@angular/core\";\r\nimport { SubSink } from \"./sub-sink\";\r\nimport { Subject } from \"rxjs\";\r\n\r\n/**\r\n * A class that automatically unsubscribes all observables when the object gets destroyed\r\n */\r\n@Injectable()\r\nexport class UnsubscribeOnDestroyAdapter implements OnDestroy {\r\n /**\r\n * The subscription sink object that stores all subscriptions\r\n */\r\n subs = new SubSink();\r\n destroy$ = new Subject<void>();\r\n\r\n /**\r\n * The lifecycle hook that unsubscribes all subscriptions when the component / object gets destroyed\r\n */\r\n ngOnDestroy(): void {\r\n this.subs?.unsubscribe();\r\n this.destroy$.next();\r\n this.destroy$.complete();\r\n }\r\n}\r\n","import {\r\n patchState,\r\n signalStore,\r\n withMethods,\r\n withComputed,\r\n withState,\r\n signalStoreFeature\r\n} from \"@ngrx/signals\";\r\nimport { computed } from \"@angular/core\";\r\n\r\nexport interface BaseStoreState<TData, TCustom = {}> {\r\n loading: Record<any, boolean>;\r\n error: Record<any, any>;\r\n data: Record<any, TData>;\r\n custom?: TCustom;\r\n}\r\n\r\nexport function withBaseStore<TData, TCustom = {}>(\r\n initialState: BaseStoreState<TData, TCustom>\r\n) {\r\n return signalStoreFeature(\r\n withState<BaseStoreState<TData, TCustom>>(initialState),\r\n\r\n withComputed(store => ({\r\n keys: computed(() => Object.keys(store.data?.() ?? {})),\r\n getLoading: computed(() => (key: string) => !!store.loading?.()?.[key]),\r\n getError: computed(() => (key: string) => store.error?.()?.[key] ?? null),\r\n getData: computed(() => (key: string) => store.data?.()?.[key] ?? null),\r\n getCustom: computed(() => store.custom?.())\r\n })),\r\n\r\n withMethods(store => ({\r\n setData: (key: string, data: TData) => {\r\n patchState(store, {\r\n data: { ...(store.data?.() ?? {}), [key]: data }\r\n });\r\n },\r\n setLoading: (key: string, loading: boolean) => {\r\n patchState(store, {\r\n loading: { ...(store.loading?.() ?? {}), [key]: loading }\r\n });\r\n },\r\n setError: (key: string, error: any) => {\r\n patchState(store, {\r\n error: { ...(store.error?.() ?? {}), [key]: error }\r\n });\r\n },\r\n clearKey: (key: string) => {\r\n const currentLoading = store.loading?.() ?? {};\r\n const currentError = store.error?.() ?? {};\r\n const currentData = store.data?.() ?? {};\r\n\r\n const { [key]: _l, ...restLoading } = currentLoading;\r\n const { [key]: _e, ...restError } = currentError;\r\n const { [key]: _d, ...restData } = currentData;\r\n\r\n patchState(store, {\r\n loading: restLoading,\r\n error: restError,\r\n data: restData\r\n });\r\n },\r\n\r\n clearAll: () => {\r\n patchState(store, { ...(initialState as any) });\r\n }\r\n }))\r\n );\r\n}\r\n","/*\r\n * Public API Surface of view/shared\r\n */\r\n\r\n// Components\r\nexport * from './lib/components';\r\nexport * from './lib/components/tree-select/tree-select.interface';\r\n\r\n// Services\r\nexport * from './lib/services/auth/auth-dialog.service';\r\n\r\n// Utilities\r\nexport * from './lib/sub-sink';\r\nexport * from './lib/UnsubscribeOnDestroyAdapter';\r\nexport * from './lib/base.store';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i2","i1","i3"],"mappings":"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"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OnDestroy } from "@angular/core";
|
|
2
|
+
import { SubSink } from "./sub-sink";
|
|
3
|
+
import { Subject } from "rxjs";
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
/**
|
|
6
|
+
* A class that automatically unsubscribes all observables when the object gets destroyed
|
|
7
|
+
*/
|
|
8
|
+
export declare class UnsubscribeOnDestroyAdapter implements OnDestroy {
|
|
9
|
+
/**
|
|
10
|
+
* The subscription sink object that stores all subscriptions
|
|
11
|
+
*/
|
|
12
|
+
subs: SubSink;
|
|
13
|
+
destroy$: Subject<void>;
|
|
14
|
+
/**
|
|
15
|
+
* The lifecycle hook that unsubscribes all subscriptions when the component / object gets destroyed
|
|
16
|
+
*/
|
|
17
|
+
ngOnDestroy(): void;
|
|
18
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<UnsubscribeOnDestroyAdapter, never>;
|
|
19
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<UnsubscribeOnDestroyAdapter>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface BaseStoreState<TData, TCustom = {}> {
|
|
2
|
+
loading: Record<any, boolean>;
|
|
3
|
+
error: Record<any, any>;
|
|
4
|
+
data: Record<any, TData>;
|
|
5
|
+
custom?: TCustom;
|
|
6
|
+
}
|
|
7
|
+
export declare function withBaseStore<TData, TCustom = {}>(initialState: BaseStoreState<TData, TCustom>): import("@ngrx/signals").SignalStoreFeature<import("@ngrx/signals").EmptyFeatureResult, {
|
|
8
|
+
state: {
|
|
9
|
+
loading: Record<any, boolean>;
|
|
10
|
+
error: Record<any, any>;
|
|
11
|
+
data: Record<any, TData>;
|
|
12
|
+
custom?: TCustom;
|
|
13
|
+
};
|
|
14
|
+
props: {
|
|
15
|
+
keys: import("@angular/core").Signal<string[]>;
|
|
16
|
+
getLoading: import("@angular/core").Signal<(key: string) => boolean>;
|
|
17
|
+
getError: import("@angular/core").Signal<(key: string) => any>;
|
|
18
|
+
getData: import("@angular/core").Signal<(key: string) => TData>;
|
|
19
|
+
getCustom: import("@angular/core").Signal<TCustom>;
|
|
20
|
+
};
|
|
21
|
+
methods: {
|
|
22
|
+
setData: (key: string, data: TData) => void;
|
|
23
|
+
setLoading: (key: string, loading: boolean) => void;
|
|
24
|
+
setError: (key: string, error: any) => void;
|
|
25
|
+
clearKey: (key: string) => void;
|
|
26
|
+
clearAll: () => void;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { EventEmitter, OnInit } from "@angular/core";
|
|
2
|
+
import { ChangeDetectorRef, NgZone } from "@angular/core";
|
|
3
|
+
import { FileService } from "@web-portal/core-infrastructure";
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
export declare class ExcelUploadComponent implements OnInit {
|
|
6
|
+
private fileService;
|
|
7
|
+
private cdr;
|
|
8
|
+
private ngZone;
|
|
9
|
+
fileInputRef: any;
|
|
10
|
+
ngOnInit(): void;
|
|
11
|
+
selectedFiles?: FileList;
|
|
12
|
+
currentFile?: File;
|
|
13
|
+
progress: number;
|
|
14
|
+
message: string;
|
|
15
|
+
filePath: string;
|
|
16
|
+
absoluteURL: string;
|
|
17
|
+
onChange: EventEmitter<any>;
|
|
18
|
+
fileAttr: string;
|
|
19
|
+
constructor(fileService: FileService, cdr: ChangeDetectorRef, ngZone: NgZone);
|
|
20
|
+
handleInputChange(event: any): void;
|
|
21
|
+
triggerFileInput(): void;
|
|
22
|
+
onChooseClick(event: Event): void;
|
|
23
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ExcelUploadComponent, never>;
|
|
24
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ExcelUploadComponent, "app-excel-upload", never, {}, { "onChange": "onChange"; }, never, never, true, never>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EventEmitter, OnChanges, OnDestroy, OnInit, SimpleChanges } from "@angular/core";
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
type MediaKind = "image" | "file" | "video";
|
|
4
|
+
export declare class MediaUploadComponent implements OnInit, OnChanges, OnDestroy {
|
|
5
|
+
private readonly fileWap_GCSService;
|
|
6
|
+
private readonly messageService;
|
|
7
|
+
kind: MediaKind;
|
|
8
|
+
label: string;
|
|
9
|
+
placeholder: string;
|
|
10
|
+
accept: string | null;
|
|
11
|
+
maxSizeMb: number | null;
|
|
12
|
+
initialUrl: string | null;
|
|
13
|
+
uploaded: EventEmitter<{
|
|
14
|
+
filePath: string;
|
|
15
|
+
absoluteUrl?: string;
|
|
16
|
+
duration?: number;
|
|
17
|
+
size?: number;
|
|
18
|
+
}>;
|
|
19
|
+
removed: EventEmitter<void>;
|
|
20
|
+
uploadingChange: EventEmitter<boolean>;
|
|
21
|
+
progress: number;
|
|
22
|
+
previewUrl: string | null;
|
|
23
|
+
storagePath: string | null;
|
|
24
|
+
fileSize: number;
|
|
25
|
+
fileName: string;
|
|
26
|
+
videoDurationText: string;
|
|
27
|
+
ngOnInit(): void;
|
|
28
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
29
|
+
ngOnDestroy(): void;
|
|
30
|
+
get acceptMime(): string;
|
|
31
|
+
get maxBytes(): number;
|
|
32
|
+
onSelect(event: any): void;
|
|
33
|
+
remove(): void;
|
|
34
|
+
private upload;
|
|
35
|
+
private setPreviewFromPath;
|
|
36
|
+
private loadVideoDuration;
|
|
37
|
+
private getVideoDurationFromUrl;
|
|
38
|
+
private error;
|
|
39
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<MediaUploadComponent, never>;
|
|
40
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<MediaUploadComponent, "app-media-upload", never, { "kind": { "alias": "kind"; "required": false; }; "label": { "alias": "label"; "required": false; }; "placeholder": { "alias": "placeholder"; "required": false; }; "accept": { "alias": "accept"; "required": false; }; "maxSizeMb": { "alias": "maxSizeMb"; "required": false; }; "initialUrl": { "alias": "initialUrl"; "required": false; }; }, { "uploaded": "uploaded"; "removed": "removed"; "uploadingChange": "uploadingChange"; }, never, never, true, never>;
|
|
41
|
+
}
|
|
42
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { DynamicDialogRef } from "primeng/dynamicdialog";
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class ReLoginDialogComponent {
|
|
4
|
+
private ref;
|
|
5
|
+
constructor(ref: DynamicDialogRef);
|
|
6
|
+
onConfirm(): void;
|
|
7
|
+
onCancel(): void;
|
|
8
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ReLoginDialogComponent, never>;
|
|
9
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ReLoginDialogComponent, "app-relogin-dialog", never, {}, {}, never, never, true, never>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EventEmitter, OnInit, OnDestroy, OnChanges, SimpleChanges } from "@angular/core";
|
|
2
|
+
import { TreeNode } from "./tree-select.interface";
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
export declare class TreeSelectComponent implements OnInit, OnDestroy, OnChanges {
|
|
5
|
+
form: any;
|
|
6
|
+
controlName: string;
|
|
7
|
+
dataSource: TreeNode[];
|
|
8
|
+
onChange: EventEmitter<any>;
|
|
9
|
+
private readonly destroy$;
|
|
10
|
+
readonly selectedNodes: import("@angular/core").WritableSignal<any[]>;
|
|
11
|
+
readonly treeNodes: import("@angular/core").WritableSignal<TreeNode[]>;
|
|
12
|
+
ngOnInit(): void;
|
|
13
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
14
|
+
ngOnDestroy(): void;
|
|
15
|
+
onSelectionChange(event: any): void;
|
|
16
|
+
private extractStoreIds;
|
|
17
|
+
getSelectedStoreIds(): number[];
|
|
18
|
+
clearSelection(): void;
|
|
19
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TreeSelectComponent, never>;
|
|
20
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<TreeSelectComponent, "tree-select", never, { "form": { "alias": "form"; "required": false; }; "controlName": { "alias": "controlName"; "required": false; }; "dataSource": { "alias": "dataSource"; "required": false; }; }, { "onChange": "onChange"; }, never, never, true, never>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { IAuthDialogService } from "@web-portal/core-infrastructure";
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class AuthDialogService implements IAuthDialogService {
|
|
4
|
+
private dialogService;
|
|
5
|
+
private activeRef;
|
|
6
|
+
openReLoginDialog(): Promise<boolean>;
|
|
7
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AuthDialogService, never>;
|
|
8
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<AuthDialogService>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { SubscriptionLike } from "rxjs";
|
|
2
|
+
/**
|
|
3
|
+
* Subscription sink that holds Observable subscriptions
|
|
4
|
+
* until you call unsubscribe on it in ngOnDestroy.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SubSink {
|
|
7
|
+
protected _subs: SubscriptionLike[];
|
|
8
|
+
/**
|
|
9
|
+
* Subscription sink that holds Observable subscriptions
|
|
10
|
+
* until you call unsubscribe on it in ngOnDestroy.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* In Angular:
|
|
14
|
+
* ```
|
|
15
|
+
* private subs = new SubSink();
|
|
16
|
+
* ...
|
|
17
|
+
* this.subs.sink = observable$.subscribe(
|
|
18
|
+
* this.subs.add(observable$.subscribe(...));
|
|
19
|
+
* ...
|
|
20
|
+
* ngOnDestroy() {
|
|
21
|
+
* this.subs.unsubscribe();
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Add subscriptions to the tracked subscriptions
|
|
27
|
+
* @example
|
|
28
|
+
* this.subs.add(observable$.subscribe(...));
|
|
29
|
+
*/
|
|
30
|
+
add(...subscriptions: SubscriptionLike[]): void;
|
|
31
|
+
/**
|
|
32
|
+
* Assign subscription to this sink to add it to the tracked subscriptions
|
|
33
|
+
* @example
|
|
34
|
+
* this.subs.sink = observable$.subscribe(...);
|
|
35
|
+
*/
|
|
36
|
+
set sink(subscription: SubscriptionLike);
|
|
37
|
+
/**
|
|
38
|
+
* Unsubscribe to all subscriptions in ngOnDestroy()
|
|
39
|
+
* @example
|
|
40
|
+
* ngOnDestroy() {
|
|
41
|
+
* this.subs.unsubscribe();
|
|
42
|
+
* }
|
|
43
|
+
*/
|
|
44
|
+
unsubscribe(): void;
|
|
45
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@web-portal/view-shared",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"peerDependencies": {
|
|
5
|
+
"@angular/common": "^19.2.0",
|
|
6
|
+
"@angular/core": "^19.2.0",
|
|
7
|
+
"primeng": "^19.0.0"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"tslib": "^2.3.0"
|
|
11
|
+
},
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"module": "fesm2022/web-portal-view-shared.mjs",
|
|
14
|
+
"typings": "index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./package.json": {
|
|
17
|
+
"default": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./index.d.ts",
|
|
21
|
+
"default": "./fesm2022/web-portal-view-shared.mjs"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/public-api.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from './lib/components';
|
|
2
|
+
export * from './lib/components/tree-select/tree-select.interface';
|
|
3
|
+
export * from './lib/services/auth/auth-dialog.service';
|
|
4
|
+
export * from './lib/sub-sink';
|
|
5
|
+
export * from './lib/UnsubscribeOnDestroyAdapter';
|
|
6
|
+
export * from './lib/base.store';
|