@web-portal/view-shared 0.0.1 → 0.0.2

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.
@@ -1,14 +1,15 @@
1
1
  import { HttpEventType, HttpResponse } from '@angular/common/http';
2
2
  import * as i0 from '@angular/core';
3
- import { EventEmitter, Output, ViewChild, Component, inject, Input, signal, Injectable, computed } from '@angular/core';
3
+ import { EventEmitter, Output, ViewChild, Component, inject, Input, signal, Injectable, computed, effect } from '@angular/core';
4
4
  import * as i1$2 from '@angular/forms';
5
5
  import { FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
6
6
  import * as i1$1 from '@angular/common';
7
- import { CommonModule } from '@angular/common';
7
+ import { CommonModule, DatePipe } from '@angular/common';
8
8
  import * as i3 from 'primeng/button';
9
9
  import { ButtonModule } from 'primeng/button';
10
10
  import * as i4 from 'primeng/progressbar';
11
11
  import { ProgressBarModule } from 'primeng/progressbar';
12
+ import * as i7 from 'primeng/inputtext';
12
13
  import { InputTextModule } from 'primeng/inputtext';
13
14
  import * as i1 from '@web-portal/core-infrastructure';
14
15
  import { FileWAPGCSService } from '@web-portal/core-infrastructure';
@@ -21,7 +22,18 @@ import { TreeSelectModule } from 'primeng/treeselect';
21
22
  import { Subject } from 'rxjs';
22
23
  import * as i1$3 from 'primeng/dynamicdialog';
23
24
  import { DialogService } from 'primeng/dynamicdialog';
24
- import { signalStoreFeature, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
25
+ import * as i3$2 from 'primeng/dialog';
26
+ import { DialogModule } from 'primeng/dialog';
27
+ import * as i5 from 'primeng/table';
28
+ import { TableModule } from 'primeng/table';
29
+ import * as i8 from 'primeng/checkbox';
30
+ import { CheckboxModule } from 'primeng/checkbox';
31
+ import { signalStore, withProps, withMethods, withHooks, signalStoreFeature, withState, withComputed, patchState } from '@ngrx/signals';
32
+ import { BaseApiService, buildKey, withBaseStore as withBaseStore$1 } from '@cci-web-app/core';
33
+ import { CORE_GATEWAY_CONFIG_TOKEN } from '@web-portal/core-base';
34
+ import { StoreHelper } from '@web-portal/core-domain';
35
+ import * as i10 from 'primeng/calendar';
36
+ import { CalendarModule } from 'primeng/calendar';
25
37
 
26
38
  class ExcelUploadComponent {
27
39
  ngOnInit() { }
@@ -441,6 +453,1345 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
441
453
  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
454
  }], ctorParameters: () => [{ type: i1$3.DynamicDialogRef }] });
443
455
 
456
+ class CategoryTreeApiService extends BaseApiService {
457
+ constructor() {
458
+ const gatewayConfig = inject(CORE_GATEWAY_CONFIG_TOKEN);
459
+ super({
460
+ baseUrl: `/api/ProductCategories`,
461
+ gatewayApi: gatewayConfig.MDM_API
462
+ });
463
+ }
464
+ list() {
465
+ return this.get(`${this.baseUrl}`, null, true);
466
+ }
467
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CategoryTreeApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
468
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CategoryTreeApiService, providedIn: "root" }); }
469
+ }
470
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: CategoryTreeApiService, decorators: [{
471
+ type: Injectable,
472
+ args: [{
473
+ providedIn: "root"
474
+ }]
475
+ }], ctorParameters: () => [] });
476
+
477
+ const initialState = {
478
+ loading: {},
479
+ error: {},
480
+ data: {}
481
+ };
482
+ const CATEGORY_TREE_STORE_KEY = {
483
+ LIST: buildKey({ feature: "category-tree", scope: "default", device: "all" }),
484
+ USER_LIST: buildKey({ feature: "category-tree", scope: "user", device: "all" })
485
+ };
486
+ const CategoryTreeStore = signalStore({ providedIn: "root" }, withBaseStore$1(initialState), withProps(() => ({
487
+ _categoryTreeApiService: inject(CategoryTreeApiService)
488
+ })), withMethods(store => ({
489
+ list: StoreHelper.createRxMethod(store, ({ key }) => key ?? CATEGORY_TREE_STORE_KEY.LIST, () => store._categoryTreeApiService.list())
490
+ })), withHooks({
491
+ onInit() { },
492
+ onDestroy() { }
493
+ }));
494
+
495
+ class ProductPopupApiService extends BaseApiService {
496
+ constructor() {
497
+ const gatewayConfig = inject(CORE_GATEWAY_CONFIG_TOKEN);
498
+ super({
499
+ baseUrl: `/api/Products`,
500
+ gatewayApi: gatewayConfig.SCM_API,
501
+ });
502
+ }
503
+ popupSearch(payload) {
504
+ return this.post(`${this.baseUrl}/PopupSearch`, payload);
505
+ }
506
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ProductPopupApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
507
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ProductPopupApiService, providedIn: "root" }); }
508
+ }
509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ProductPopupApiService, decorators: [{
510
+ type: Injectable,
511
+ args: [{ providedIn: "root" }]
512
+ }], ctorParameters: () => [] });
513
+
514
+ class ProductPickerDialogComponent {
515
+ set visible(value) {
516
+ this.isDialogOpen.set(value);
517
+ if (value) {
518
+ this.pageIndex = 0;
519
+ this.allProducts = [];
520
+ this.totalRecords = 0;
521
+ this.remoteLoaded = false;
522
+ this.selectedProducts = [];
523
+ }
524
+ }
525
+ set products(value) {
526
+ this.initialProducts = value ?? [];
527
+ if (!this.remoteLoaded) {
528
+ this.allProducts = [...this.initialProducts];
529
+ this.totalRecords = this.allProducts.length;
530
+ }
531
+ }
532
+ constructor(fb, productPopupApi, cdr) {
533
+ this.fb = fb;
534
+ this.productPopupApi = productPopupApi;
535
+ this.cdr = cdr;
536
+ this.title = 'Chọn sản phẩm';
537
+ this.selectionMode = 'single';
538
+ this.visibleChange = new EventEmitter();
539
+ this.select = new EventEmitter();
540
+ this.selectMultiple = new EventEmitter();
541
+ // Dialog state using signal pattern
542
+ this.isDialogOpen = signal(false);
543
+ this.isDialogVisible = computed(() => this.isDialogOpen());
544
+ this.allProducts = [];
545
+ this.selectedProducts = [];
546
+ this.totalRecords = 0;
547
+ this.pageIndex = 0;
548
+ this.pageSize = 10;
549
+ this.loading = false;
550
+ this.categoryTreeNodes = [];
551
+ this.remoteLoaded = false;
552
+ this.initialProducts = [];
553
+ this.searchForm = this.fb.group({
554
+ keySearch: [''],
555
+ categoryIds: [[]],
556
+ });
557
+ this.categoryTreeStore = inject(CategoryTreeStore);
558
+ this.categoryTreeWatcher = effect(() => {
559
+ const categoryData = this.categoryTreeStore.getData()(CATEGORY_TREE_STORE_KEY.LIST);
560
+ if (Array.isArray(categoryData) && categoryData.length > 0) {
561
+ const nodes = this.buildTreeFromFlat(categoryData);
562
+ queueMicrotask(() => {
563
+ this.categoryTreeNodes = nodes;
564
+ this.cdr.markForCheck();
565
+ });
566
+ }
567
+ });
568
+ }
569
+ ngOnInit() {
570
+ if (this.categoryTreeStore.list) {
571
+ this.categoryTreeStore.list({ key: CATEGORY_TREE_STORE_KEY.LIST });
572
+ }
573
+ }
574
+ onCategorySelectionChange() {
575
+ // Close dropdown after a short delay to allow selection to complete
576
+ setTimeout(() => {
577
+ if (this.categoryTreeSelect && typeof this.categoryTreeSelect.hide === 'function') {
578
+ this.categoryTreeSelect.hide();
579
+ }
580
+ }, 150);
581
+ }
582
+ onDialogClose() {
583
+ this.isDialogOpen.set(false);
584
+ this.visibleChange.emit(false);
585
+ }
586
+ onVisibleChange(visible) {
587
+ if (!visible) {
588
+ this.onDialogClose();
589
+ }
590
+ }
591
+ onConfirm() {
592
+ if (this.selectedProducts.length === 0) {
593
+ return;
594
+ }
595
+ // Emit all selected products
596
+ this.selectMultiple.emit([...this.selectedProducts]);
597
+ // Also emit the last selected product for backward compatibility
598
+ if (this.selectedProducts.length > 0) {
599
+ this.select.emit(this.selectedProducts[this.selectedProducts.length - 1]);
600
+ }
601
+ // Close dialog after confirmation
602
+ this.onDialogClose();
603
+ }
604
+ onCheckboxClick(event) {
605
+ event.stopPropagation();
606
+ }
607
+ onRowClick(event, product) {
608
+ event.stopPropagation();
609
+ // In single mode, select immediately and close dialog
610
+ if (this.selectionMode === 'single') {
611
+ const target = event.target;
612
+ if (target.closest('p-checkbox') || target.closest('.p-checkbox')) {
613
+ return;
614
+ }
615
+ // Select product and close
616
+ this.select.emit(product);
617
+ this.onDialogClose();
618
+ }
619
+ }
620
+ isSelected(product) {
621
+ return this.selectedProducts.some((p) => this.isSameProduct(p, product));
622
+ }
623
+ toggleSelect(product, checked) {
624
+ if (checked) {
625
+ if (!this.isSelected(product)) {
626
+ this.selectedProducts.push(product);
627
+ }
628
+ }
629
+ else {
630
+ this.selectedProducts = this.selectedProducts.filter((p) => !this.isSameProduct(p, product));
631
+ }
632
+ }
633
+ getCurrentPageProducts() {
634
+ const start = this.pageIndex * this.pageSize;
635
+ const end = start + this.pageSize;
636
+ return this.allProducts.slice(start, end);
637
+ }
638
+ getProductId(product) {
639
+ return product.id || (product.raw?.PID ? String(product.raw.PID) : '');
640
+ }
641
+ isSameProduct(p1, p2) {
642
+ const id1 = this.getProductId(p1);
643
+ const id2 = this.getProductId(p2);
644
+ if (id1 && id2) {
645
+ return id1 === id2;
646
+ }
647
+ return p1.raw?.PID === p2.raw?.PID && p1.raw?.PID != null;
648
+ }
649
+ isAllSelected() {
650
+ const currentPageProducts = this.getCurrentPageProducts();
651
+ if (currentPageProducts.length === 0) {
652
+ return false;
653
+ }
654
+ return currentPageProducts.every((product) => this.isSelected(product));
655
+ }
656
+ toggleSelectAll(checked) {
657
+ const currentPageProducts = this.getCurrentPageProducts();
658
+ if (checked) {
659
+ currentPageProducts.forEach((product) => {
660
+ if (!this.isSelected(product)) {
661
+ this.selectedProducts.push(product);
662
+ }
663
+ });
664
+ }
665
+ else {
666
+ const currentPageProductIds = new Set(currentPageProducts.map(p => this.getProductId(p)));
667
+ this.selectedProducts = this.selectedProducts.filter((p) => !currentPageProductIds.has(this.getProductId(p)));
668
+ }
669
+ }
670
+ onSearch(resetPage = false) {
671
+ if (resetPage) {
672
+ this.pageIndex = 0;
673
+ }
674
+ this.fetchProducts();
675
+ }
676
+ onPageChange(event) {
677
+ if (event?.first === undefined) {
678
+ return;
679
+ }
680
+ if (event.rows !== undefined && event.rows !== this.pageSize) {
681
+ this.pageSize = event.rows;
682
+ }
683
+ this.pageIndex = Math.floor(event.first / this.pageSize);
684
+ }
685
+ fetchProducts() {
686
+ const payload = this.buildSearchPayload();
687
+ this.loading = true;
688
+ this.productPopupApi.popupSearch(payload).subscribe({
689
+ next: (response) => {
690
+ const items = this.extractResponseItems(response).map((item) => this.mapProduct(item));
691
+ this.allProducts = items;
692
+ this.totalRecords = this.resolveTotalRecords(response, items.length);
693
+ this.remoteLoaded = true;
694
+ this.loading = false;
695
+ },
696
+ error: () => {
697
+ this.loading = false;
698
+ },
699
+ });
700
+ }
701
+ mapProduct(item) {
702
+ const code = item.ProductID ?? String(item.PID ?? '');
703
+ return {
704
+ id: code,
705
+ code,
706
+ name: item.ProductName ?? code,
707
+ price: item.SalePriceAfter ?? item.SalePrice ?? undefined,
708
+ raw: item,
709
+ };
710
+ }
711
+ extractResponseItems(response) {
712
+ if (!response) {
713
+ return [];
714
+ }
715
+ if (Array.isArray(response)) {
716
+ return response;
717
+ }
718
+ return (response.items ||
719
+ response.data ||
720
+ response.results ||
721
+ response.list ||
722
+ response.payload ||
723
+ []);
724
+ }
725
+ resolveTotalRecords(response, fallback) {
726
+ if (!response) {
727
+ return fallback;
728
+ }
729
+ return (response.totalItems ??
730
+ response.totalRecords ??
731
+ response.total ??
732
+ response.count ??
733
+ fallback);
734
+ }
735
+ buildSearchPayload() {
736
+ const { keySearch, categoryIds } = this.searchForm.value;
737
+ return {
738
+ KeySearch: keySearch?.trim() || "",
739
+ CategoryIDs: this.extractCategoryIds(categoryIds).map((id) => id.toString()),
740
+ IsActived: true,
741
+ PageIndex: 0,
742
+ PageSize: this.pageSize,
743
+ };
744
+ }
745
+ extractCategoryIds(selection) {
746
+ if (!selection) {
747
+ return [];
748
+ }
749
+ const nodes = Array.isArray(selection) ? selection : [selection];
750
+ return nodes
751
+ .map((node) => this.getNodeCategoryId(node))
752
+ .filter((value) => value !== null);
753
+ }
754
+ getNodeCategoryId(node) {
755
+ if (!node) {
756
+ return null;
757
+ }
758
+ if (typeof node === "number") {
759
+ return node;
760
+ }
761
+ if (typeof node === "string" && !Number.isNaN(Number(node))) {
762
+ return Number(node);
763
+ }
764
+ return (node?.data?.categoryId ??
765
+ node?.data?.id ??
766
+ node?.categoryId ??
767
+ node?.id ??
768
+ null);
769
+ }
770
+ buildTreeFromFlat(flatData) {
771
+ const nodeMap = new Map();
772
+ const roots = [];
773
+ flatData.forEach((item) => {
774
+ const id = this.extractCategoryId(item);
775
+ if (id == null) {
776
+ return;
777
+ }
778
+ const label = item.CategoryName ??
779
+ `ID ${id}`;
780
+ nodeMap.set(id, {
781
+ key: `category_${id}`,
782
+ label: String(label),
783
+ data: {
784
+ categoryId: item.CategoryID,
785
+ name: label,
786
+ },
787
+ children: [],
788
+ selectable: true,
789
+ });
790
+ });
791
+ flatData.forEach((item) => {
792
+ const id = this.extractCategoryId(item);
793
+ if (id == null) {
794
+ return;
795
+ }
796
+ const parentId = this.extractParentCategoryId(item);
797
+ const node = nodeMap.get(id);
798
+ if (!node) {
799
+ return;
800
+ }
801
+ if (parentId == null || !nodeMap.has(parentId)) {
802
+ roots.push(node);
803
+ }
804
+ else {
805
+ const parentNode = nodeMap.get(parentId);
806
+ parentNode?.children?.push(node);
807
+ }
808
+ });
809
+ return roots.length > 0 ? roots : Array.from(nodeMap.values());
810
+ }
811
+ extractCategoryId(item) {
812
+ return (item.CategoryID ??
813
+ null);
814
+ }
815
+ extractParentCategoryId(item) {
816
+ return (item.ParentCategoryID ??
817
+ null);
818
+ }
819
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ProductPickerDialogComponent, deps: [{ token: i1$2.FormBuilder }, { token: ProductPopupApiService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
820
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.14", type: ProductPickerDialogComponent, isStandalone: true, selector: "app-product-picker-dialog", inputs: { title: "title", visible: "visible", products: "products", selectionMode: "selectionMode" }, outputs: { visibleChange: "visibleChange", select: "select", selectMultiple: "selectMultiple" }, viewQueries: [{ propertyName: "categoryTreeSelect", first: true, predicate: ["categoryTreeSelect"], descendants: true }], ngImport: i0, template: `
821
+ <p-dialog
822
+ [header]="title"
823
+ [visible]="isDialogVisible()"
824
+ [modal]="true"
825
+ [dismissableMask]="false"
826
+ [style]="{ width: '950px' }"
827
+ [closable]="true"
828
+ [draggable]="false"
829
+ [resizable]="false"
830
+ (onHide)="onDialogClose()"
831
+ (visibleChange)="onVisibleChange($event)"
832
+ [closeOnEscape]="true"
833
+ appendTo="body"
834
+ >
835
+ <div>
836
+ <form [formGroup]="searchForm" class="grid grid-cols-1 gap-4 md:grid-cols-3 items-end mb-4">
837
+ <div class="md:col-span-1 w-full">
838
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Từ khóa</label>
839
+ <input
840
+ pInputText
841
+ formControlName="keySearch"
842
+ placeholder="Nhập mã hoặc tên sản phẩm"
843
+ class="w-full"
844
+ />
845
+ </div>
846
+
847
+ <div class="md:col-span-1 w-full">
848
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Ngành hàng</label>
849
+ <p-treeSelect
850
+ #categoryTreeSelect
851
+ formControlName="categoryIds"
852
+ [options]="categoryTreeNodes"
853
+ [selectionMode]="'checkbox'"
854
+ [display]="'chip'"
855
+ [showClear]="true"
856
+ placeholder="Chọn ngành hàng"
857
+ [panelStyle]="{ width: '100%' }"
858
+ panelStyleClass="!w-auto !max-w-full min-w-0"
859
+ styleClass="w-full"
860
+ fluid
861
+ inputStyleClass="w-full"
862
+ appendTo="body"
863
+ (onSelectionChange)="onCategorySelectionChange()"
864
+ ></p-treeSelect>
865
+ </div>
866
+
867
+ <div class="md:col-span-1 flex justify-end">
868
+ <p-button
869
+ type="button"
870
+ label="Tìm kiếm"
871
+ icon="pi pi-search"
872
+ [loading]="loading"
873
+ (onClick)="onSearch(true)"
874
+ styleClass="w-full md:w-auto"
875
+ ></p-button>
876
+ </div>
877
+ </form>
878
+
879
+ <p-table
880
+ [value]="allProducts"
881
+ [rows]="pageSize"
882
+ [paginator]="true"
883
+ [rowsPerPageOptions]="[10, 20, 30, 50]"
884
+ [totalRecords]="totalRecords"
885
+ [first]="pageIndex * pageSize"
886
+ [lazy]="false"
887
+ [loading]="loading"
888
+ responsiveLayout="scroll"
889
+ currentPageReportTemplate="Hiển thị {first} - {last} / {totalRecords}"
890
+ (onPage)="onPageChange($event)"
891
+ >
892
+ <ng-template pTemplate="header">
893
+ <tr>
894
+ @if (selectionMode === 'multiple') {
895
+ <th class="w-12 text-center">
896
+ <p-checkbox
897
+ [binary]="true"
898
+ [ngModel]="isAllSelected()"
899
+ (ngModelChange)="toggleSelectAll($event)"
900
+ (click)="onCheckboxClick($event)"
901
+ [inputId]="'selectAll'"
902
+ ></p-checkbox>
903
+ </th>
904
+ }
905
+ <th class="w-32">Mã sản phẩm</th>
906
+ <th class="w-32">Mã tham chiếu</th>
907
+ <th>Tên sản phẩm</th>
908
+ <th class="w-70">Ngành hàng</th>
909
+ </tr>
910
+ </ng-template>
911
+ <ng-template pTemplate="body" let-product>
912
+ <tr class="hover:bg-gray-50 cursor-pointer" (click)="onRowClick($event, product)">
913
+ @if (selectionMode === 'multiple') {
914
+ <td class="text-center">
915
+ <p-checkbox
916
+ [binary]="true"
917
+ [ngModel]="isSelected(product)"
918
+ (ngModelChange)="toggleSelect(product, $event)"
919
+ (click)="onCheckboxClick($event)"
920
+ [inputId]="'select-' + product.id"
921
+ ></p-checkbox>
922
+ </td>
923
+ }
924
+ <td>{{ product.raw?.ProductID || product.code || '—' }}</td>
925
+ <td>{{ product.raw?.ReferenceID || '—' }}</td>
926
+ <td>
927
+ <div class="font-semibold text-gray-900">{{ product.name || product.raw?.ProductName || '—' }}</div>
928
+ </td>
929
+ <td>{{ product.raw?.CategoryName || '—' }}</td>
930
+ </tr>
931
+ </ng-template>
932
+ <ng-template pTemplate="emptymessage">
933
+ <tr>
934
+ <td [attr.colspan]="selectionMode === 'multiple' ? 5 : 4" class="text-center text-gray-500 py-4">
935
+ Không tìm thấy sản phẩm phù hợp
936
+ </td>
937
+ </tr>
938
+ </ng-template>
939
+ </p-table>
940
+ </div>
941
+ <ng-template pTemplate="footer">
942
+ <div class="flex items-center justify-between">
943
+ @if (selectionMode === 'multiple') {
944
+ <div class="text-sm text-gray-600">
945
+ Đã chọn: <span class="font-semibold">{{ selectedProducts.length }}</span> sản phẩm
946
+ </div>
947
+ } @else {
948
+ <div class="text-sm text-gray-600">
949
+ Click vào sản phẩm để chọn
950
+ </div>
951
+ }
952
+ <div class="flex gap-2">
953
+ <p-button
954
+ label="Hủy"
955
+ icon="pi pi-times"
956
+ text
957
+ (onClick)="onDialogClose()"
958
+ />
959
+ @if (selectionMode === 'multiple') {
960
+ <p-button
961
+ label="Xác nhận"
962
+ icon="pi pi-check"
963
+ severity="primary"
964
+ [disabled]="selectedProducts.length === 0"
965
+ (onClick)="onConfirm()"
966
+ />
967
+ }
968
+ </div>
969
+ </div>
970
+ </ng-template>
971
+ </p-dialog>
972
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3$2.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i5.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "style", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "scrollDirection", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "responsive", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "autoLayout", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "virtualRowHeight", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { 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: InputTextModule }, { kind: "directive", type: i7.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i8.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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"] }] }); }
973
+ }
974
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: ProductPickerDialogComponent, decorators: [{
975
+ type: Component,
976
+ args: [{
977
+ selector: 'app-product-picker-dialog',
978
+ standalone: true,
979
+ imports: [
980
+ CommonModule,
981
+ DialogModule,
982
+ TableModule,
983
+ ButtonModule,
984
+ InputTextModule,
985
+ CheckboxModule,
986
+ ReactiveFormsModule,
987
+ FormsModule,
988
+ TreeSelectModule
989
+ ],
990
+ template: `
991
+ <p-dialog
992
+ [header]="title"
993
+ [visible]="isDialogVisible()"
994
+ [modal]="true"
995
+ [dismissableMask]="false"
996
+ [style]="{ width: '950px' }"
997
+ [closable]="true"
998
+ [draggable]="false"
999
+ [resizable]="false"
1000
+ (onHide)="onDialogClose()"
1001
+ (visibleChange)="onVisibleChange($event)"
1002
+ [closeOnEscape]="true"
1003
+ appendTo="body"
1004
+ >
1005
+ <div>
1006
+ <form [formGroup]="searchForm" class="grid grid-cols-1 gap-4 md:grid-cols-3 items-end mb-4">
1007
+ <div class="md:col-span-1 w-full">
1008
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Từ khóa</label>
1009
+ <input
1010
+ pInputText
1011
+ formControlName="keySearch"
1012
+ placeholder="Nhập mã hoặc tên sản phẩm"
1013
+ class="w-full"
1014
+ />
1015
+ </div>
1016
+
1017
+ <div class="md:col-span-1 w-full">
1018
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Ngành hàng</label>
1019
+ <p-treeSelect
1020
+ #categoryTreeSelect
1021
+ formControlName="categoryIds"
1022
+ [options]="categoryTreeNodes"
1023
+ [selectionMode]="'checkbox'"
1024
+ [display]="'chip'"
1025
+ [showClear]="true"
1026
+ placeholder="Chọn ngành hàng"
1027
+ [panelStyle]="{ width: '100%' }"
1028
+ panelStyleClass="!w-auto !max-w-full min-w-0"
1029
+ styleClass="w-full"
1030
+ fluid
1031
+ inputStyleClass="w-full"
1032
+ appendTo="body"
1033
+ (onSelectionChange)="onCategorySelectionChange()"
1034
+ ></p-treeSelect>
1035
+ </div>
1036
+
1037
+ <div class="md:col-span-1 flex justify-end">
1038
+ <p-button
1039
+ type="button"
1040
+ label="Tìm kiếm"
1041
+ icon="pi pi-search"
1042
+ [loading]="loading"
1043
+ (onClick)="onSearch(true)"
1044
+ styleClass="w-full md:w-auto"
1045
+ ></p-button>
1046
+ </div>
1047
+ </form>
1048
+
1049
+ <p-table
1050
+ [value]="allProducts"
1051
+ [rows]="pageSize"
1052
+ [paginator]="true"
1053
+ [rowsPerPageOptions]="[10, 20, 30, 50]"
1054
+ [totalRecords]="totalRecords"
1055
+ [first]="pageIndex * pageSize"
1056
+ [lazy]="false"
1057
+ [loading]="loading"
1058
+ responsiveLayout="scroll"
1059
+ currentPageReportTemplate="Hiển thị {first} - {last} / {totalRecords}"
1060
+ (onPage)="onPageChange($event)"
1061
+ >
1062
+ <ng-template pTemplate="header">
1063
+ <tr>
1064
+ @if (selectionMode === 'multiple') {
1065
+ <th class="w-12 text-center">
1066
+ <p-checkbox
1067
+ [binary]="true"
1068
+ [ngModel]="isAllSelected()"
1069
+ (ngModelChange)="toggleSelectAll($event)"
1070
+ (click)="onCheckboxClick($event)"
1071
+ [inputId]="'selectAll'"
1072
+ ></p-checkbox>
1073
+ </th>
1074
+ }
1075
+ <th class="w-32">Mã sản phẩm</th>
1076
+ <th class="w-32">Mã tham chiếu</th>
1077
+ <th>Tên sản phẩm</th>
1078
+ <th class="w-70">Ngành hàng</th>
1079
+ </tr>
1080
+ </ng-template>
1081
+ <ng-template pTemplate="body" let-product>
1082
+ <tr class="hover:bg-gray-50 cursor-pointer" (click)="onRowClick($event, product)">
1083
+ @if (selectionMode === 'multiple') {
1084
+ <td class="text-center">
1085
+ <p-checkbox
1086
+ [binary]="true"
1087
+ [ngModel]="isSelected(product)"
1088
+ (ngModelChange)="toggleSelect(product, $event)"
1089
+ (click)="onCheckboxClick($event)"
1090
+ [inputId]="'select-' + product.id"
1091
+ ></p-checkbox>
1092
+ </td>
1093
+ }
1094
+ <td>{{ product.raw?.ProductID || product.code || '—' }}</td>
1095
+ <td>{{ product.raw?.ReferenceID || '—' }}</td>
1096
+ <td>
1097
+ <div class="font-semibold text-gray-900">{{ product.name || product.raw?.ProductName || '—' }}</div>
1098
+ </td>
1099
+ <td>{{ product.raw?.CategoryName || '—' }}</td>
1100
+ </tr>
1101
+ </ng-template>
1102
+ <ng-template pTemplate="emptymessage">
1103
+ <tr>
1104
+ <td [attr.colspan]="selectionMode === 'multiple' ? 5 : 4" class="text-center text-gray-500 py-4">
1105
+ Không tìm thấy sản phẩm phù hợp
1106
+ </td>
1107
+ </tr>
1108
+ </ng-template>
1109
+ </p-table>
1110
+ </div>
1111
+ <ng-template pTemplate="footer">
1112
+ <div class="flex items-center justify-between">
1113
+ @if (selectionMode === 'multiple') {
1114
+ <div class="text-sm text-gray-600">
1115
+ Đã chọn: <span class="font-semibold">{{ selectedProducts.length }}</span> sản phẩm
1116
+ </div>
1117
+ } @else {
1118
+ <div class="text-sm text-gray-600">
1119
+ Click vào sản phẩm để chọn
1120
+ </div>
1121
+ }
1122
+ <div class="flex gap-2">
1123
+ <p-button
1124
+ label="Hủy"
1125
+ icon="pi pi-times"
1126
+ text
1127
+ (onClick)="onDialogClose()"
1128
+ />
1129
+ @if (selectionMode === 'multiple') {
1130
+ <p-button
1131
+ label="Xác nhận"
1132
+ icon="pi pi-check"
1133
+ severity="primary"
1134
+ [disabled]="selectedProducts.length === 0"
1135
+ (onClick)="onConfirm()"
1136
+ />
1137
+ }
1138
+ </div>
1139
+ </div>
1140
+ </ng-template>
1141
+ </p-dialog>
1142
+ `,
1143
+ }]
1144
+ }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: ProductPopupApiService }, { type: i0.ChangeDetectorRef }], propDecorators: { title: [{
1145
+ type: Input
1146
+ }], visible: [{
1147
+ type: Input
1148
+ }], products: [{
1149
+ type: Input
1150
+ }], selectionMode: [{
1151
+ type: Input
1152
+ }], visibleChange: [{
1153
+ type: Output
1154
+ }], select: [{
1155
+ type: Output
1156
+ }], selectMultiple: [{
1157
+ type: Output
1158
+ }], categoryTreeSelect: [{
1159
+ type: ViewChild,
1160
+ args: ['categoryTreeSelect']
1161
+ }] } });
1162
+
1163
+ class PromotionApiService extends BaseApiService {
1164
+ constructor() {
1165
+ const gatewayConfig = inject(CORE_GATEWAY_CONFIG_TOKEN);
1166
+ super({
1167
+ baseUrl: `/api/Promotions`,
1168
+ gatewayApi: gatewayConfig.SCM_API
1169
+ });
1170
+ }
1171
+ search(payload) {
1172
+ return this.post(`${this.baseUrl}/Search`, payload);
1173
+ }
1174
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PromotionApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1175
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PromotionApiService, providedIn: "root" }); }
1176
+ }
1177
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PromotionApiService, decorators: [{
1178
+ type: Injectable,
1179
+ args: [{ providedIn: "root" }]
1180
+ }], ctorParameters: () => [] });
1181
+
1182
+ class PromotionPickerDialogComponent {
1183
+ set visible(value) {
1184
+ this.isDialogOpen.set(value);
1185
+ if (value) {
1186
+ this.pageIndex = 0;
1187
+ this.allPromotions = [];
1188
+ this.totalRecords = 0;
1189
+ this.remoteLoaded = false;
1190
+ // Reset form với giá trị mặc định
1191
+ this.searchForm.patchValue({
1192
+ dateFrom: this.getDefaultDateFrom(),
1193
+ dateTo: this.getDefaultDateTo()
1194
+ });
1195
+ }
1196
+ }
1197
+ constructor(fb, promotionApiService, cdr) {
1198
+ this.fb = fb;
1199
+ this.promotionApiService = promotionApiService;
1200
+ this.cdr = cdr;
1201
+ this.title = 'Chọn chương trình khuyến mãi';
1202
+ this.selectionMode = 'single';
1203
+ this.visibleChange = new EventEmitter();
1204
+ this.select = new EventEmitter();
1205
+ this.selectMultiple = new EventEmitter();
1206
+ // Dialog state using signal pattern
1207
+ this.isDialogOpen = signal(false);
1208
+ this.isDialogVisible = computed(() => this.isDialogOpen());
1209
+ this.allPromotions = [];
1210
+ this.selectedPromotions = [];
1211
+ this.totalRecords = 0;
1212
+ this.pageIndex = 0;
1213
+ this.pageSize = 10;
1214
+ this.loading = false;
1215
+ this.categoryTreeNodes = [];
1216
+ this.promotionTypeOptions = [];
1217
+ this.remoteLoaded = false;
1218
+ this.searchForm = this.fb.group({
1219
+ keySearch: [''],
1220
+ typeSearch: 2,
1221
+ categoryIDs: [[]],
1222
+ dateFrom: [this.getDefaultDateFrom()],
1223
+ dateTo: [this.getDefaultDateTo()]
1224
+ });
1225
+ this.categoryTreeStore = inject(CategoryTreeStore);
1226
+ this.categoryTreeWatcher = effect(() => {
1227
+ const categoryData = this.categoryTreeStore.getData()(CATEGORY_TREE_STORE_KEY.LIST);
1228
+ if (Array.isArray(categoryData) && categoryData.length > 0) {
1229
+ const nodes = this.buildTreeFromFlat(categoryData);
1230
+ queueMicrotask(() => {
1231
+ this.categoryTreeNodes = nodes;
1232
+ this.cdr.markForCheck();
1233
+ });
1234
+ }
1235
+ });
1236
+ }
1237
+ getDefaultDateFrom() {
1238
+ const date = new Date();
1239
+ date.setDate(date.getDate() - 30);
1240
+ date.setHours(0, 0, 0, 0);
1241
+ return date;
1242
+ }
1243
+ getDefaultDateTo() {
1244
+ const date = new Date();
1245
+ date.setHours(23, 59, 59, 999);
1246
+ return date;
1247
+ }
1248
+ ngOnInit() {
1249
+ if (this.categoryTreeStore.list) {
1250
+ this.categoryTreeStore.list({ key: CATEGORY_TREE_STORE_KEY.LIST });
1251
+ }
1252
+ }
1253
+ onCategorySelectionChange() {
1254
+ // Close dropdown after a short delay to allow selection to complete
1255
+ setTimeout(() => {
1256
+ if (this.categoryTreeSelect && typeof this.categoryTreeSelect.hide === 'function') {
1257
+ this.categoryTreeSelect.hide();
1258
+ }
1259
+ }, 150);
1260
+ }
1261
+ onDialogClose() {
1262
+ this.isDialogOpen.set(false);
1263
+ this.visibleChange.emit(false);
1264
+ }
1265
+ onVisibleChange(visible) {
1266
+ if (!visible) {
1267
+ this.onDialogClose();
1268
+ }
1269
+ }
1270
+ onRowClick(event, promotion) {
1271
+ event.stopPropagation();
1272
+ if (this.selectionMode === 'single') {
1273
+ this.select.emit(promotion);
1274
+ this.onDialogClose();
1275
+ }
1276
+ }
1277
+ onSearch(resetPage = false) {
1278
+ if (resetPage) {
1279
+ this.pageIndex = 0;
1280
+ }
1281
+ this.fetchPromotions();
1282
+ }
1283
+ onPageChange(event) {
1284
+ if (event?.first === undefined) {
1285
+ return;
1286
+ }
1287
+ if (event.rows !== undefined && event.rows !== this.pageSize) {
1288
+ this.pageSize = event.rows;
1289
+ }
1290
+ this.pageIndex = Math.floor(event.first / this.pageSize);
1291
+ this.fetchPromotions();
1292
+ }
1293
+ fetchPromotions() {
1294
+ const payload = this.buildSearchPayload();
1295
+ this.loading = true;
1296
+ this.promotionApiService.search(payload).subscribe({
1297
+ next: (response) => {
1298
+ const items = (response.Records || []).map((item) => this.mapPromotion(item));
1299
+ this.allPromotions = items;
1300
+ this.totalRecords = response.TotalRecord || items.length;
1301
+ this.remoteLoaded = true;
1302
+ this.loading = false;
1303
+ },
1304
+ error: () => {
1305
+ this.loading = false;
1306
+ },
1307
+ });
1308
+ }
1309
+ mapPromotion(item) {
1310
+ const id = String(item.PromotionID);
1311
+ return {
1312
+ id,
1313
+ code: item.PromotionNo || id,
1314
+ name: item.PromotionName || id,
1315
+ raw: item,
1316
+ };
1317
+ }
1318
+ buildSearchPayload() {
1319
+ const { keySearch, categoryIDs, dateFrom, dateTo } = this.searchForm.value;
1320
+ return {
1321
+ KeySearch: keySearch?.trim() || "",
1322
+ CreatedUser: -1,
1323
+ DateFrom: dateFrom ? this.formatDateWithTime(dateFrom) : undefined,
1324
+ DateTo: dateTo ? this.formatDateWithTime(dateTo, true) : undefined,
1325
+ StoreID: -1,
1326
+ PromotionTypeID: -1,
1327
+ CategoryIDs: this.extractCategoryIds(categoryIDs),
1328
+ TypeSearch: 2,
1329
+ OptionSearch: -1,
1330
+ PageIndex: this.pageIndex, // API expects 1-based index
1331
+ PageSize: this.pageSize,
1332
+ IsStopped: -1,
1333
+ IsActived: -1
1334
+ };
1335
+ }
1336
+ formatDateWithTime(date, isEndDate = false) {
1337
+ const d = new Date(date);
1338
+ if (isNaN(d.getTime()))
1339
+ return "";
1340
+ const month = String(d.getMonth() + 1).padStart(2, "0");
1341
+ const day = String(d.getDate()).padStart(2, "0");
1342
+ const year = d.getFullYear();
1343
+ const hours = String(d.getHours()).padStart(2, "0");
1344
+ const minutes = String(d.getMinutes()).padStart(2, "0");
1345
+ if (isEndDate) {
1346
+ // End date: set to 23:59
1347
+ return `${month}/${day}/${year} 23:59`;
1348
+ }
1349
+ // Start date: use actual time or 00:00
1350
+ return `${month}/${day}/${year} ${hours}:${minutes}`;
1351
+ }
1352
+ extractCategoryIds(selection) {
1353
+ if (!selection) {
1354
+ return [];
1355
+ }
1356
+ const nodes = Array.isArray(selection) ? selection : [selection];
1357
+ return nodes
1358
+ .map((node) => this.getNodeCategoryId(node))
1359
+ .filter((value) => value !== null);
1360
+ }
1361
+ getNodeCategoryId(node) {
1362
+ if (!node) {
1363
+ return null;
1364
+ }
1365
+ if (typeof node === "number") {
1366
+ return node;
1367
+ }
1368
+ if (typeof node === "string" && !Number.isNaN(Number(node))) {
1369
+ return Number(node);
1370
+ }
1371
+ return (node?.data?.categoryId ??
1372
+ node?.data?.id ??
1373
+ node?.categoryId ??
1374
+ node?.id ??
1375
+ null);
1376
+ }
1377
+ buildTreeFromFlat(flatData) {
1378
+ const nodeMap = new Map();
1379
+ const roots = [];
1380
+ flatData.forEach((item) => {
1381
+ const id = this.extractCategoryId(item);
1382
+ if (id == null) {
1383
+ return;
1384
+ }
1385
+ const label = item.CategoryName ?? `ID ${id}`;
1386
+ nodeMap.set(id, {
1387
+ key: `category_${id}`,
1388
+ label: String(label),
1389
+ data: {
1390
+ categoryId: item.CategoryID,
1391
+ name: label,
1392
+ },
1393
+ children: [],
1394
+ selectable: true,
1395
+ });
1396
+ });
1397
+ flatData.forEach((item) => {
1398
+ const id = this.extractCategoryId(item);
1399
+ if (id == null) {
1400
+ return;
1401
+ }
1402
+ const parentId = this.extractParentCategoryId(item);
1403
+ const node = nodeMap.get(id);
1404
+ if (!node) {
1405
+ return;
1406
+ }
1407
+ if (parentId == null || !nodeMap.has(parentId)) {
1408
+ roots.push(node);
1409
+ }
1410
+ else {
1411
+ const parentNode = nodeMap.get(parentId);
1412
+ parentNode?.children?.push(node);
1413
+ }
1414
+ });
1415
+ return roots.length > 0 ? roots : Array.from(nodeMap.values());
1416
+ }
1417
+ extractCategoryId(item) {
1418
+ return item.CategoryID ?? null;
1419
+ }
1420
+ extractParentCategoryId(item) {
1421
+ return item.ParentCategoryID ?? null;
1422
+ }
1423
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PromotionPickerDialogComponent, deps: [{ token: i1$2.FormBuilder }, { token: PromotionApiService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); }
1424
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: PromotionPickerDialogComponent, isStandalone: true, selector: "app-promotion-picker-dialog", inputs: { title: "title", visible: "visible", selectionMode: "selectionMode" }, outputs: { visibleChange: "visibleChange", select: "select", selectMultiple: "selectMultiple" }, viewQueries: [{ propertyName: "categoryTreeSelect", first: true, predicate: ["categoryTreeSelect"], descendants: true }], ngImport: i0, template: `
1425
+ <p-dialog
1426
+ [header]="title"
1427
+ [visible]="isDialogVisible()"
1428
+ [modal]="true"
1429
+ [dismissableMask]="false"
1430
+ [style]="{ width: '95vw', maxWidth: '1400px' }"
1431
+ [closable]="true"
1432
+ [draggable]="false"
1433
+ [resizable]="false"
1434
+ (onHide)="onDialogClose()"
1435
+ (visibleChange)="onVisibleChange($event)"
1436
+ [closeOnEscape]="true"
1437
+ appendTo="body"
1438
+ >
1439
+ <div>
1440
+ <!-- Search Form -->
1441
+ <form [formGroup]="searchForm" class="mb-4">
1442
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-end">
1443
+ <!-- Chuỗi tìm -->
1444
+ <div>
1445
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Chuỗi tìm</label>
1446
+ <input
1447
+ pInputText
1448
+ formControlName="keySearch"
1449
+ placeholder="Nhập từ khóa"
1450
+ class="w-full"
1451
+ />
1452
+ </div>
1453
+
1454
+ <!-- Ngành hàng -->
1455
+ <div>
1456
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Ngành hàng</label>
1457
+ <p-treeSelect
1458
+ #categoryTreeSelect
1459
+ formControlName="categoryIDs"
1460
+ [options]="categoryTreeNodes"
1461
+ [selectionMode]="'checkbox'"
1462
+ [display]="'chip'"
1463
+ [showClear]="true"
1464
+ placeholder="Chọn ngành hàng"
1465
+ [panelStyle]="{ width: '100%' }"
1466
+ panelStyleClass="!w-auto !max-w-full min-w-0"
1467
+ styleClass="w-full"
1468
+ fluid
1469
+ inputStyleClass="w-full"
1470
+ appendTo="body"
1471
+ (onSelectionChange)="onCategorySelectionChange()">
1472
+ </p-treeSelect>
1473
+ </div>
1474
+
1475
+ <!-- Chạy từ ngày -->
1476
+ <div>
1477
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Chạy từ ngày</label>
1478
+ <p-calendar
1479
+ formControlName="dateFrom"
1480
+ [showIcon]="true"
1481
+ [showTime]="true"
1482
+ [hourFormat]="'24'"
1483
+ dateFormat="dd/mm/yy"
1484
+ styleClass="w-full"
1485
+ appendTo="body"
1486
+ placeholder="dd/MM/yyyy HH:mm">
1487
+ </p-calendar>
1488
+ </div>
1489
+
1490
+ <!-- Đến ngày -->
1491
+ <div>
1492
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Đến ngày</label>
1493
+ <p-calendar
1494
+ formControlName="dateTo"
1495
+ [showIcon]="true"
1496
+ [showTime]="true"
1497
+ [hourFormat]="'24'"
1498
+ dateFormat="dd/mm/yy"
1499
+ styleClass="w-full"
1500
+ appendTo="body"
1501
+ placeholder="dd/MM/yyyy HH:mm">
1502
+ </p-calendar>
1503
+ </div>
1504
+ </div>
1505
+
1506
+ <!-- Search Button -->
1507
+ <div class="flex justify-end mt-4">
1508
+ <p-button
1509
+ type="button"
1510
+ label="Tìm kiếm"
1511
+ icon="pi pi-search"
1512
+ [loading]="loading"
1513
+ (onClick)="onSearch(true)"
1514
+ severity="primary"
1515
+ />
1516
+ </div>
1517
+ </form>
1518
+
1519
+ <!-- Table -->
1520
+ <p-table
1521
+ [value]="allPromotions"
1522
+ [rows]="pageSize"
1523
+ [paginator]="true"
1524
+ [rowsPerPageOptions]="[10, 20, 30, 50]"
1525
+ [totalRecords]="totalRecords"
1526
+ [first]="pageIndex * pageSize"
1527
+ [lazy]="true"
1528
+ [loading]="loading"
1529
+ responsiveLayout="scroll"
1530
+ currentPageReportTemplate="Hiển thị {first} - {last} / {totalRecords}"
1531
+ (onPage)="onPageChange($event)"
1532
+ >
1533
+ <ng-template pTemplate="header">
1534
+ <tr>
1535
+ <th class="w-32">Mã CTKM</th>
1536
+ <th>Tên chương trình khuyến mãi</th>
1537
+ <th class="w-32">Ngày bắt đầu</th>
1538
+ <th class="w-32">Ngày kết thúc</th>
1539
+ <th class="w-40">Số ngày bắt đầu chạy</th>
1540
+ <th class="w-48">Loại chương trình</th>
1541
+ <th class="w-48">Nhân viên tạo</th>
1542
+ <th class="w-24 text-center">Duyệt cấp 1</th>
1543
+ <th class="w-24 text-center">Duyệt cấp 2</th>
1544
+ </tr>
1545
+ </ng-template>
1546
+ <ng-template pTemplate="body" let-promotion>
1547
+ <tr class="hover:bg-gray-50 cursor-pointer" (click)="onRowClick($event, promotion)">
1548
+ <td>{{ promotion.raw?.PromotionNo || '—' }}</td>
1549
+ <td>
1550
+ <div class="font-semibold text-gray-900">{{ promotion.raw?.PromotionName || '—' }}</div>
1551
+ </td>
1552
+ <td>{{ promotion.raw?.BeginDate ? (promotion.raw.BeginDate | date:'dd/MM/yyyy') : '—' }}</td>
1553
+ <td>{{ promotion.raw?.EndDate ? (promotion.raw.EndDate | date:'dd/MM/yyyy') : '—' }}</td>
1554
+ <td>{{ promotion.raw?.RunStatus || '—' }}</td>
1555
+ <td>{{ promotion.raw?.PromotionTypeName || '—' }}</td>
1556
+ <td>{{ promotion.raw?.CreatedUser_FullName || '—' }}</td>
1557
+ <td class="text-center">
1558
+ <p-checkbox [binary]="true" [ngModel]="promotion.raw?.IsActivedOne" [disabled]="true" [inputId]="'actived1-' + promotion.id"></p-checkbox>
1559
+ </td>
1560
+ <td class="text-center">
1561
+ <p-checkbox [binary]="true" [ngModel]="promotion.raw?.IsActivedTwo" [disabled]="true" [inputId]="'actived2-' + promotion.id"></p-checkbox>
1562
+ </td>
1563
+ </tr>
1564
+ </ng-template>
1565
+ <ng-template pTemplate="emptymessage">
1566
+ <tr>
1567
+ <td colspan="9" class="text-center text-gray-500 py-4">
1568
+ Không tìm thấy chương trình khuyến mãi phù hợp
1569
+ </td>
1570
+ </tr>
1571
+ </ng-template>
1572
+ </p-table>
1573
+ </div>
1574
+ <ng-template pTemplate="footer">
1575
+ <div class="flex items-center justify-between">
1576
+ <div class="text-sm text-gray-600">
1577
+ Click vào chương trình khuyến mãi để chọn
1578
+ </div>
1579
+ <div class="flex gap-2">
1580
+ <p-button
1581
+ label="Hủy"
1582
+ icon="pi pi-times"
1583
+ text
1584
+ (onClick)="onDialogClose()"
1585
+ />
1586
+ </div>
1587
+ </div>
1588
+ </ng-template>
1589
+ </p-dialog>
1590
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i3$2.Dialog, selector: "p-dialog", inputs: ["header", "draggable", "resizable", "positionLeft", "positionTop", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "responsive", "appendTo", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "breakpoint", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "directive", type: i3$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: TableModule }, { kind: "component", type: i5.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "style", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "scrollDirection", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "responsive", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "autoLayout", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "virtualRowHeight", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { 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: InputTextModule }, { kind: "directive", type: i7.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: CheckboxModule }, { kind: "component", type: i8.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["value", "name", "disabled", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "style", "inputStyle", "styleClass", "inputClass", "indeterminate", "size", "formControl", "checkboxIcon", "readonly", "required", "autofocus", "trueValue", "falseValue", "variant"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "ngmodule", type: CalendarModule }, { kind: "component", type: i10.Calendar, selector: "p-calendar", inputs: ["iconDisplay", "style", "styleClass", "inputStyle", "inputId", "name", "inputStyleClass", "placeholder", "ariaLabelledBy", "ariaLabel", "iconAriaLabel", "disabled", "dateFormat", "multipleSeparator", "rangeSeparator", "inline", "showOtherMonths", "selectOtherMonths", "showIcon", "fluid", "icon", "appendTo", "readonlyInput", "shortYearCutoff", "monthNavigator", "yearNavigator", "hourFormat", "timeOnly", "stepHour", "stepMinute", "stepSecond", "showSeconds", "required", "showOnFocus", "showWeek", "startWeekFromFirstDayOfYear", "showClear", "dataType", "selectionMode", "maxDateCount", "showButtonBar", "todayButtonStyleClass", "clearButtonStyleClass", "autofocus", "autoZIndex", "baseZIndex", "panelStyleClass", "panelStyle", "keepInvalid", "hideOnDateTimeSelect", "touchUI", "timeSeparator", "focusTrap", "showTransitionOptions", "hideTransitionOptions", "tabindex", "variant", "minDate", "maxDate", "disabledDates", "disabledDays", "yearRange", "showTime", "responsiveOptions", "numberOfMonths", "firstDayOfWeek", "locale", "view", "defaultDate"], outputs: ["onFocus", "onBlur", "onClose", "onSelect", "onClear", "onInput", "onTodayClick", "onClearClick", "onMonthChange", "onYearChange", "onClickOutside", "onShow"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { 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"] }] }); }
1591
+ }
1592
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: PromotionPickerDialogComponent, decorators: [{
1593
+ type: Component,
1594
+ args: [{
1595
+ selector: 'app-promotion-picker-dialog',
1596
+ standalone: true,
1597
+ imports: [
1598
+ CommonModule,
1599
+ DialogModule,
1600
+ TableModule,
1601
+ ButtonModule,
1602
+ InputTextModule,
1603
+ CheckboxModule,
1604
+ CalendarModule,
1605
+ ReactiveFormsModule,
1606
+ FormsModule,
1607
+ TreeSelectModule,
1608
+ DatePipe
1609
+ ],
1610
+ template: `
1611
+ <p-dialog
1612
+ [header]="title"
1613
+ [visible]="isDialogVisible()"
1614
+ [modal]="true"
1615
+ [dismissableMask]="false"
1616
+ [style]="{ width: '95vw', maxWidth: '1400px' }"
1617
+ [closable]="true"
1618
+ [draggable]="false"
1619
+ [resizable]="false"
1620
+ (onHide)="onDialogClose()"
1621
+ (visibleChange)="onVisibleChange($event)"
1622
+ [closeOnEscape]="true"
1623
+ appendTo="body"
1624
+ >
1625
+ <div>
1626
+ <!-- Search Form -->
1627
+ <form [formGroup]="searchForm" class="mb-4">
1628
+ <div class="grid grid-cols-1 md:grid-cols-4 gap-4 items-end">
1629
+ <!-- Chuỗi tìm -->
1630
+ <div>
1631
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Chuỗi tìm</label>
1632
+ <input
1633
+ pInputText
1634
+ formControlName="keySearch"
1635
+ placeholder="Nhập từ khóa"
1636
+ class="w-full"
1637
+ />
1638
+ </div>
1639
+
1640
+ <!-- Ngành hàng -->
1641
+ <div>
1642
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Ngành hàng</label>
1643
+ <p-treeSelect
1644
+ #categoryTreeSelect
1645
+ formControlName="categoryIDs"
1646
+ [options]="categoryTreeNodes"
1647
+ [selectionMode]="'checkbox'"
1648
+ [display]="'chip'"
1649
+ [showClear]="true"
1650
+ placeholder="Chọn ngành hàng"
1651
+ [panelStyle]="{ width: '100%' }"
1652
+ panelStyleClass="!w-auto !max-w-full min-w-0"
1653
+ styleClass="w-full"
1654
+ fluid
1655
+ inputStyleClass="w-full"
1656
+ appendTo="body"
1657
+ (onSelectionChange)="onCategorySelectionChange()">
1658
+ </p-treeSelect>
1659
+ </div>
1660
+
1661
+ <!-- Chạy từ ngày -->
1662
+ <div>
1663
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Chạy từ ngày</label>
1664
+ <p-calendar
1665
+ formControlName="dateFrom"
1666
+ [showIcon]="true"
1667
+ [showTime]="true"
1668
+ [hourFormat]="'24'"
1669
+ dateFormat="dd/mm/yy"
1670
+ styleClass="w-full"
1671
+ appendTo="body"
1672
+ placeholder="dd/MM/yyyy HH:mm">
1673
+ </p-calendar>
1674
+ </div>
1675
+
1676
+ <!-- Đến ngày -->
1677
+ <div>
1678
+ <label class="block text-sm font-semibold text-gray-700 mb-2">Đến ngày</label>
1679
+ <p-calendar
1680
+ formControlName="dateTo"
1681
+ [showIcon]="true"
1682
+ [showTime]="true"
1683
+ [hourFormat]="'24'"
1684
+ dateFormat="dd/mm/yy"
1685
+ styleClass="w-full"
1686
+ appendTo="body"
1687
+ placeholder="dd/MM/yyyy HH:mm">
1688
+ </p-calendar>
1689
+ </div>
1690
+ </div>
1691
+
1692
+ <!-- Search Button -->
1693
+ <div class="flex justify-end mt-4">
1694
+ <p-button
1695
+ type="button"
1696
+ label="Tìm kiếm"
1697
+ icon="pi pi-search"
1698
+ [loading]="loading"
1699
+ (onClick)="onSearch(true)"
1700
+ severity="primary"
1701
+ />
1702
+ </div>
1703
+ </form>
1704
+
1705
+ <!-- Table -->
1706
+ <p-table
1707
+ [value]="allPromotions"
1708
+ [rows]="pageSize"
1709
+ [paginator]="true"
1710
+ [rowsPerPageOptions]="[10, 20, 30, 50]"
1711
+ [totalRecords]="totalRecords"
1712
+ [first]="pageIndex * pageSize"
1713
+ [lazy]="true"
1714
+ [loading]="loading"
1715
+ responsiveLayout="scroll"
1716
+ currentPageReportTemplate="Hiển thị {first} - {last} / {totalRecords}"
1717
+ (onPage)="onPageChange($event)"
1718
+ >
1719
+ <ng-template pTemplate="header">
1720
+ <tr>
1721
+ <th class="w-32">Mã CTKM</th>
1722
+ <th>Tên chương trình khuyến mãi</th>
1723
+ <th class="w-32">Ngày bắt đầu</th>
1724
+ <th class="w-32">Ngày kết thúc</th>
1725
+ <th class="w-40">Số ngày bắt đầu chạy</th>
1726
+ <th class="w-48">Loại chương trình</th>
1727
+ <th class="w-48">Nhân viên tạo</th>
1728
+ <th class="w-24 text-center">Duyệt cấp 1</th>
1729
+ <th class="w-24 text-center">Duyệt cấp 2</th>
1730
+ </tr>
1731
+ </ng-template>
1732
+ <ng-template pTemplate="body" let-promotion>
1733
+ <tr class="hover:bg-gray-50 cursor-pointer" (click)="onRowClick($event, promotion)">
1734
+ <td>{{ promotion.raw?.PromotionNo || '—' }}</td>
1735
+ <td>
1736
+ <div class="font-semibold text-gray-900">{{ promotion.raw?.PromotionName || '—' }}</div>
1737
+ </td>
1738
+ <td>{{ promotion.raw?.BeginDate ? (promotion.raw.BeginDate | date:'dd/MM/yyyy') : '—' }}</td>
1739
+ <td>{{ promotion.raw?.EndDate ? (promotion.raw.EndDate | date:'dd/MM/yyyy') : '—' }}</td>
1740
+ <td>{{ promotion.raw?.RunStatus || '—' }}</td>
1741
+ <td>{{ promotion.raw?.PromotionTypeName || '—' }}</td>
1742
+ <td>{{ promotion.raw?.CreatedUser_FullName || '—' }}</td>
1743
+ <td class="text-center">
1744
+ <p-checkbox [binary]="true" [ngModel]="promotion.raw?.IsActivedOne" [disabled]="true" [inputId]="'actived1-' + promotion.id"></p-checkbox>
1745
+ </td>
1746
+ <td class="text-center">
1747
+ <p-checkbox [binary]="true" [ngModel]="promotion.raw?.IsActivedTwo" [disabled]="true" [inputId]="'actived2-' + promotion.id"></p-checkbox>
1748
+ </td>
1749
+ </tr>
1750
+ </ng-template>
1751
+ <ng-template pTemplate="emptymessage">
1752
+ <tr>
1753
+ <td colspan="9" class="text-center text-gray-500 py-4">
1754
+ Không tìm thấy chương trình khuyến mãi phù hợp
1755
+ </td>
1756
+ </tr>
1757
+ </ng-template>
1758
+ </p-table>
1759
+ </div>
1760
+ <ng-template pTemplate="footer">
1761
+ <div class="flex items-center justify-between">
1762
+ <div class="text-sm text-gray-600">
1763
+ Click vào chương trình khuyến mãi để chọn
1764
+ </div>
1765
+ <div class="flex gap-2">
1766
+ <p-button
1767
+ label="Hủy"
1768
+ icon="pi pi-times"
1769
+ text
1770
+ (onClick)="onDialogClose()"
1771
+ />
1772
+ </div>
1773
+ </div>
1774
+ </ng-template>
1775
+ </p-dialog>
1776
+ `,
1777
+ }]
1778
+ }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: PromotionApiService }, { type: i0.ChangeDetectorRef }], propDecorators: { title: [{
1779
+ type: Input
1780
+ }], visible: [{
1781
+ type: Input
1782
+ }], selectionMode: [{
1783
+ type: Input
1784
+ }], visibleChange: [{
1785
+ type: Output
1786
+ }], select: [{
1787
+ type: Output
1788
+ }], selectMultiple: [{
1789
+ type: Output
1790
+ }], categoryTreeSelect: [{
1791
+ type: ViewChild,
1792
+ args: ['categoryTreeSelect']
1793
+ }] } });
1794
+
444
1795
  class AuthDialogService {
445
1796
  constructor() {
446
1797
  this.dialogService = inject(DialogService);
@@ -611,5 +1962,5 @@ function withBaseStore(initialState) {
611
1962
  * Generated bundle index. Do not edit.
612
1963
  */
613
1964
 
614
- export { AuthDialogService, ExcelUploadComponent, MediaUploadComponent, ReLoginDialogComponent, SubSink, TreeSelectComponent, UnsubscribeOnDestroyAdapter, withBaseStore };
1965
+ export { AuthDialogService, CATEGORY_TREE_STORE_KEY, CategoryTreeApiService, CategoryTreeStore, ExcelUploadComponent, MediaUploadComponent, ProductPickerDialogComponent, ProductPopupApiService, PromotionApiService, PromotionPickerDialogComponent, ReLoginDialogComponent, SubSink, TreeSelectComponent, UnsubscribeOnDestroyAdapter, withBaseStore };
615
1966
  //# sourceMappingURL=web-portal-view-shared.mjs.map