osl-base-extended 1.1.55 → 1.1.58
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.
|
@@ -2603,6 +2603,8 @@ class OslSetup {
|
|
|
2603
2603
|
formFooterTpl;
|
|
2604
2604
|
customFooterWrapperTpl;
|
|
2605
2605
|
searchbar;
|
|
2606
|
+
gridRef;
|
|
2607
|
+
cardContainerRef;
|
|
2606
2608
|
// ── Inputs ────────────────────────────────────────────────────
|
|
2607
2609
|
title = '';
|
|
2608
2610
|
columns = [];
|
|
@@ -2613,8 +2615,7 @@ class OslSetup {
|
|
|
2613
2615
|
tableHeight = 'calc(100vh - 220px)';
|
|
2614
2616
|
totalRecords = 0;
|
|
2615
2617
|
loading = false;
|
|
2616
|
-
dialogWidth =
|
|
2617
|
-
/** Dynamic form elements — when provided, enables Add/Edit dialog and action column. */
|
|
2618
|
+
dialogWidth = '50vw';
|
|
2618
2619
|
formElements = [];
|
|
2619
2620
|
beforeDisplay;
|
|
2620
2621
|
onAddEditFn;
|
|
@@ -2628,10 +2629,14 @@ class OslSetup {
|
|
|
2628
2629
|
partialCustomHeaderTemp;
|
|
2629
2630
|
stateKey = '';
|
|
2630
2631
|
primaryKey = 'id';
|
|
2632
|
+
onSave;
|
|
2633
|
+
/** Fixed page size used for card view infinite scroll. Defaults to pageSize. */
|
|
2634
|
+
cardPageSize;
|
|
2635
|
+
/** Optional custom card template. Context: { $implicit: row, index: number } */
|
|
2636
|
+
cardTemplate;
|
|
2631
2637
|
// ── Outputs ───────────────────────────────────────────────────
|
|
2632
2638
|
onSearch = new EventEmitter();
|
|
2633
2639
|
onAdd = new EventEmitter();
|
|
2634
|
-
// @Output() onSave = new EventEmitter<OslSetupSaveEvent>();
|
|
2635
2640
|
onEdit = new EventEmitter();
|
|
2636
2641
|
onDelete = new EventEmitter();
|
|
2637
2642
|
pageChange = new EventEmitter();
|
|
@@ -2639,16 +2644,188 @@ class OslSetup {
|
|
|
2639
2644
|
sortChange = new EventEmitter();
|
|
2640
2645
|
onRowClick = new EventEmitter();
|
|
2641
2646
|
onStateRestored = new EventEmitter();
|
|
2642
|
-
gridRef;
|
|
2643
|
-
onSave;
|
|
2644
2647
|
// ── Dialog state ──────────────────────────────────────────────
|
|
2645
2648
|
dialogModel = {};
|
|
2646
2649
|
dialogMode = 'add';
|
|
2647
2650
|
_dialogRef = null;
|
|
2651
|
+
// ── View mode ─────────────────────────────────────────────────
|
|
2652
|
+
viewMode = 'table';
|
|
2653
|
+
// ── Card view state ───────────────────────────────────────────
|
|
2654
|
+
cardDatasource = [];
|
|
2655
|
+
cardPage = 0;
|
|
2656
|
+
allCardsLoaded = false;
|
|
2657
|
+
cardOpenMenuIndex = null;
|
|
2658
|
+
cardMenuPosition = { top: 0, left: 0 };
|
|
2659
|
+
_cardExpectedPage = 0;
|
|
2660
|
+
_cardRestoreTargetPage = 0;
|
|
2661
|
+
_needsInitialCardLoad = false;
|
|
2662
|
+
onDocumentClick() {
|
|
2663
|
+
this.cardOpenMenuIndex = null;
|
|
2664
|
+
}
|
|
2648
2665
|
get hasForm() {
|
|
2649
2666
|
return this.formElements?.length > 0 || !!this.onAddEditFn;
|
|
2650
2667
|
}
|
|
2668
|
+
get _effectiveCardPageSize() {
|
|
2669
|
+
return this.cardPageSize ?? this.pageSize;
|
|
2670
|
+
}
|
|
2671
|
+
get _displayColumns() {
|
|
2672
|
+
return this.columns.filter(c => !c.isActions);
|
|
2673
|
+
}
|
|
2674
|
+
get cardTitleColumn() {
|
|
2675
|
+
return this._displayColumns[0];
|
|
2676
|
+
}
|
|
2677
|
+
get cardBodyColumns() {
|
|
2678
|
+
return this._displayColumns.slice(1);
|
|
2679
|
+
}
|
|
2680
|
+
get skeletonCardRows() {
|
|
2681
|
+
return Array.from({ length: 6 });
|
|
2682
|
+
}
|
|
2683
|
+
// ── Lifecycle ─────────────────────────────────────────────────
|
|
2684
|
+
ngOnInit() {
|
|
2685
|
+
this._loadViewMode();
|
|
2686
|
+
if (this.viewMode === 'card') {
|
|
2687
|
+
this._needsInitialCardLoad = true;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2651
2690
|
ngAfterViewInit() {
|
|
2691
|
+
this.statemainTain();
|
|
2692
|
+
if (this._needsInitialCardLoad) {
|
|
2693
|
+
this._needsInitialCardLoad = false;
|
|
2694
|
+
setTimeout(() => { this._startCardLoad(); });
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
ngOnChanges(changes) {
|
|
2698
|
+
if (changes['datasource']) {
|
|
2699
|
+
if (this.viewMode === 'card' && this._cardExpectedPage !== 0) {
|
|
2700
|
+
const newData = changes['datasource'].currentValue ?? [];
|
|
2701
|
+
const isFirstPage = this._cardExpectedPage === 1;
|
|
2702
|
+
this._cardExpectedPage = 0;
|
|
2703
|
+
if (isFirstPage) {
|
|
2704
|
+
this.cardDatasource = [...newData];
|
|
2705
|
+
this.cardPage = 1;
|
|
2706
|
+
this.allCardsLoaded = false;
|
|
2707
|
+
}
|
|
2708
|
+
else if (newData.length > 0) {
|
|
2709
|
+
this.cardDatasource = [...this.cardDatasource, ...newData];
|
|
2710
|
+
this.cardPage++;
|
|
2711
|
+
}
|
|
2712
|
+
if (newData.length === 0 || (this.totalRecords > 0 && this.cardDatasource.length >= this.totalRecords)) {
|
|
2713
|
+
this.allCardsLoaded = true;
|
|
2714
|
+
}
|
|
2715
|
+
// Multi-page state restore: continue loading until target page is reached
|
|
2716
|
+
if (this._cardRestoreTargetPage > 0 && this.cardPage < this._cardRestoreTargetPage && !this.allCardsLoaded) {
|
|
2717
|
+
const nextPage = this.cardPage + 1;
|
|
2718
|
+
this._cardExpectedPage = nextPage;
|
|
2719
|
+
this.pageChange.emit({ page: nextPage, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
2720
|
+
}
|
|
2721
|
+
else if (this._cardRestoreTargetPage > 0 && (this.cardPage >= this._cardRestoreTargetPage || this.allCardsLoaded)) {
|
|
2722
|
+
this._cardRestoreTargetPage = 0;
|
|
2723
|
+
if (this._pendingScrollTop !== null) {
|
|
2724
|
+
const top = this._pendingScrollTop;
|
|
2725
|
+
this._pendingScrollTop = null;
|
|
2726
|
+
setTimeout(() => { this.cardContainerRef?.nativeElement?.scrollTo({ top }); }, 50);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
else if (this.viewMode !== 'card' && this._pendingScrollTop !== null) {
|
|
2731
|
+
const ds = changes['datasource'].currentValue;
|
|
2732
|
+
if (ds?.length > 0) {
|
|
2733
|
+
const top = this._pendingScrollTop;
|
|
2734
|
+
this._pendingScrollTop = null;
|
|
2735
|
+
setTimeout(() => { this.gridRef?.scrollTo(top); }, 50);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
// ── View persistence ──────────────────────────────────────────
|
|
2741
|
+
_getViewKey() {
|
|
2742
|
+
return this.stateKey ? `osl-view-${this.stateKey}` : '';
|
|
2743
|
+
}
|
|
2744
|
+
_loadViewMode() {
|
|
2745
|
+
const key = this._getViewKey();
|
|
2746
|
+
if (!key)
|
|
2747
|
+
return;
|
|
2748
|
+
const saved = localStorage.getItem(key);
|
|
2749
|
+
if (saved === 'card' || saved === 'table') {
|
|
2750
|
+
this.viewMode = saved;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
toggleView(mode) {
|
|
2754
|
+
if (this.viewMode === mode)
|
|
2755
|
+
return;
|
|
2756
|
+
this.viewMode = mode;
|
|
2757
|
+
const key = this._getViewKey();
|
|
2758
|
+
if (key)
|
|
2759
|
+
localStorage.setItem(key, mode);
|
|
2760
|
+
if (mode === 'card') {
|
|
2761
|
+
this._startCardLoad();
|
|
2762
|
+
}
|
|
2763
|
+
else {
|
|
2764
|
+
this._cardExpectedPage = 0;
|
|
2765
|
+
if (this.gridRef)
|
|
2766
|
+
this.gridRef.currentPage = 1;
|
|
2767
|
+
this.pageChange.emit({ page: 1, pageSize: this.pageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
// ── Card view helpers ─────────────────────────────────────────
|
|
2771
|
+
_startCardLoad() {
|
|
2772
|
+
this.cardDatasource = [];
|
|
2773
|
+
this.cardPage = 0;
|
|
2774
|
+
this.allCardsLoaded = false;
|
|
2775
|
+
this._cardRestoreTargetPage = 0;
|
|
2776
|
+
this._cardExpectedPage = 1;
|
|
2777
|
+
this.pageChange.emit({ page: 1, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
2778
|
+
}
|
|
2779
|
+
loadMoreCards() {
|
|
2780
|
+
if (this.loading || this.allCardsLoaded || this._cardExpectedPage !== 0)
|
|
2781
|
+
return;
|
|
2782
|
+
if (this.totalRecords > 0 && this.cardDatasource.length >= this.totalRecords) {
|
|
2783
|
+
this.allCardsLoaded = true;
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
const nextPage = this.cardPage + 1;
|
|
2787
|
+
this._cardExpectedPage = nextPage;
|
|
2788
|
+
this.pageChange.emit({ page: nextPage, pageSize: this._effectiveCardPageSize, searchValue: this.searchbar?.searchControl?.value ?? '' });
|
|
2789
|
+
}
|
|
2790
|
+
onCardScroll(event) {
|
|
2791
|
+
const el = event.target;
|
|
2792
|
+
if (el.scrollHeight - el.scrollTop - el.clientHeight < 150) {
|
|
2793
|
+
this.loadMoreCards();
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
isHighlightedCard(row) {
|
|
2797
|
+
if (!this.restoredRow || !this.primaryKey)
|
|
2798
|
+
return false;
|
|
2799
|
+
const val = row[this.primaryKey];
|
|
2800
|
+
return val !== undefined && val === this.restoredRow[this.primaryKey];
|
|
2801
|
+
}
|
|
2802
|
+
toggleCardMenu(index, event) {
|
|
2803
|
+
event.stopPropagation();
|
|
2804
|
+
if (this.cardOpenMenuIndex === index) {
|
|
2805
|
+
this.cardOpenMenuIndex = null;
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
2809
|
+
const menuWidth = 190;
|
|
2810
|
+
const left = Math.min(Math.max(rect.right - menuWidth, 8), window.innerWidth - menuWidth - 8);
|
|
2811
|
+
this.cardMenuPosition = { top: rect.bottom + 6, left };
|
|
2812
|
+
this.cardOpenMenuIndex = index;
|
|
2813
|
+
}
|
|
2814
|
+
getCellValue(row, col) {
|
|
2815
|
+
const raw = row[col.key];
|
|
2816
|
+
if (col.displayFn)
|
|
2817
|
+
return col.displayFn(raw, row);
|
|
2818
|
+
if (col.enums?.length) {
|
|
2819
|
+
const match = col.enums.find(e => e.value === raw);
|
|
2820
|
+
return match ? match.label : (raw ?? '--');
|
|
2821
|
+
}
|
|
2822
|
+
return raw !== null && raw !== undefined && raw !== '' ? raw : '--';
|
|
2823
|
+
}
|
|
2824
|
+
hasVisibleActions(row) {
|
|
2825
|
+
return this.moreMenuActions.some(a => !a.hideIf || !a.hideIf(row));
|
|
2826
|
+
}
|
|
2827
|
+
// ── State maintenance ─────────────────────────────────────────
|
|
2828
|
+
statemainTain() {
|
|
2652
2829
|
if (!this.stateKey)
|
|
2653
2830
|
return;
|
|
2654
2831
|
const state = this._stateService.consume(this.stateKey);
|
|
@@ -2656,48 +2833,64 @@ class OslSetup {
|
|
|
2656
2833
|
return;
|
|
2657
2834
|
this._pendingScrollTop = state.scrollTop;
|
|
2658
2835
|
this.restoredRow = state.highlightedRow;
|
|
2659
|
-
if (this.
|
|
2660
|
-
this.
|
|
2661
|
-
this.
|
|
2662
|
-
this.
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
this.
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2836
|
+
if (this.viewMode === 'card') {
|
|
2837
|
+
this._needsInitialCardLoad = false;
|
|
2838
|
+
this.cardDatasource = [];
|
|
2839
|
+
this.cardPage = 0;
|
|
2840
|
+
this.allCardsLoaded = false;
|
|
2841
|
+
this._cardRestoreTargetPage = state.page;
|
|
2842
|
+
if (this.searchbar && state.searchValue) {
|
|
2843
|
+
this._isRestoring = true;
|
|
2844
|
+
this.searchbar.searchControl.setValue(state.searchValue, { emitEvent: false });
|
|
2845
|
+
}
|
|
2846
|
+
this._cardExpectedPage = 1;
|
|
2847
|
+
if (state.searchValue) {
|
|
2848
|
+
this.onSearchSetup(state.searchValue);
|
|
2849
|
+
}
|
|
2850
|
+
else {
|
|
2851
|
+
this.pageChange.emit({ page: 1, pageSize: this._effectiveCardPageSize, searchValue: '' });
|
|
2852
|
+
}
|
|
2853
|
+
this.onStateRestored.emit(state);
|
|
2672
2854
|
}
|
|
2673
2855
|
else {
|
|
2674
|
-
|
|
2675
|
-
|
|
2856
|
+
if (this.gridRef) {
|
|
2857
|
+
this.gridRef.currentPage = state.page;
|
|
2858
|
+
this.gridRef.pageSize = state.pageSize;
|
|
2859
|
+
this.gridRef.setRestorePage(state.page);
|
|
2860
|
+
}
|
|
2861
|
+
if (this.searchbar && state.searchValue) {
|
|
2862
|
+
this._isRestoring = true;
|
|
2863
|
+
this.searchbar.searchControl.setValue(state.searchValue, { emitEvent: false });
|
|
2864
|
+
}
|
|
2865
|
+
if (state.searchValue) {
|
|
2866
|
+
this.onSearchSetup(state.searchValue);
|
|
2867
|
+
}
|
|
2868
|
+
else {
|
|
2869
|
+
this.pageChange.emit({ page: state.page, pageSize: state.pageSize, searchValue: '' });
|
|
2870
|
+
}
|
|
2871
|
+
this.onStateRestored.emit(state);
|
|
2676
2872
|
}
|
|
2677
|
-
// Also emit the dedicated restore event for apps that want fine-grained control.
|
|
2678
|
-
this.onStateRestored.emit(state);
|
|
2679
2873
|
setTimeout(() => {
|
|
2680
2874
|
this.restoredRow = null;
|
|
2681
2875
|
this.gridRef?.clearRestorePage();
|
|
2682
2876
|
}, 3000);
|
|
2683
2877
|
}
|
|
2684
|
-
|
|
2685
|
-
if (changes['datasource'] && this._pendingScrollTop !== null) {
|
|
2686
|
-
const ds = changes['datasource'].currentValue;
|
|
2687
|
-
// Only restore scroll once real data has arrived (not on intermediate empty/loading states)
|
|
2688
|
-
if (ds?.length > 0) {
|
|
2689
|
-
const scrollTop = this._pendingScrollTop;
|
|
2690
|
-
this._pendingScrollTop = null;
|
|
2691
|
-
setTimeout(() => { this.gridRef?.scrollTo(scrollTop); }, 50);
|
|
2692
|
-
}
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2878
|
+
// ── Search ────────────────────────────────────────────────────
|
|
2695
2879
|
onSearchSetup(event) {
|
|
2880
|
+
if (this.viewMode === 'card') {
|
|
2881
|
+
this.cardDatasource = [];
|
|
2882
|
+
this.cardPage = 0;
|
|
2883
|
+
this.allCardsLoaded = false;
|
|
2884
|
+
this._cardExpectedPage = 1;
|
|
2885
|
+
}
|
|
2696
2886
|
if (this._isRestoring) {
|
|
2697
|
-
|
|
2698
|
-
const restoredPage = this.gridRef?.currentPage ?? 1;
|
|
2887
|
+
const restoredPage = this.viewMode === 'card' ? 1 : (this.gridRef?.currentPage ?? 1);
|
|
2699
2888
|
this._isRestoring = false;
|
|
2700
|
-
this.pageChange.emit({
|
|
2889
|
+
this.pageChange.emit({
|
|
2890
|
+
page: restoredPage,
|
|
2891
|
+
pageSize: this.viewMode === 'card' ? this._effectiveCardPageSize : (this.gridRef?.pageSize ?? this.pageSize),
|
|
2892
|
+
searchValue: event,
|
|
2893
|
+
});
|
|
2701
2894
|
this.onSearch.emit(event);
|
|
2702
2895
|
return;
|
|
2703
2896
|
}
|
|
@@ -2707,12 +2900,12 @@ class OslSetup {
|
|
|
2707
2900
|
}
|
|
2708
2901
|
this.pageChange.emit({
|
|
2709
2902
|
page: 1,
|
|
2710
|
-
pageSize: this.gridRef?.pageSize || 10,
|
|
2711
|
-
searchValue: event
|
|
2903
|
+
pageSize: this.viewMode === 'card' ? this._effectiveCardPageSize : (this.gridRef?.pageSize || 10),
|
|
2904
|
+
searchValue: event,
|
|
2712
2905
|
});
|
|
2713
2906
|
this.onSearch.emit(event);
|
|
2714
2907
|
}
|
|
2715
|
-
|
|
2908
|
+
// ── Table helpers ─────────────────────────────────────────────
|
|
2716
2909
|
get columnsWithActions() {
|
|
2717
2910
|
if (!this.hasForm)
|
|
2718
2911
|
return this.columns;
|
|
@@ -2725,6 +2918,7 @@ class OslSetup {
|
|
|
2725
2918
|
onPageChange(eventEmitter, event) {
|
|
2726
2919
|
eventEmitter.emit({ ...event, searchValue: this.searchbar?.searchControl?.value });
|
|
2727
2920
|
}
|
|
2921
|
+
// ── Dialog actions ────────────────────────────────────────────
|
|
2728
2922
|
openAddDialog() {
|
|
2729
2923
|
this.dialogMode = 'add';
|
|
2730
2924
|
if (this.beforeDisplay) {
|
|
@@ -2744,10 +2938,12 @@ class OslSetup {
|
|
|
2744
2938
|
this.dialogMode = 'edit';
|
|
2745
2939
|
if (this.stateKey) {
|
|
2746
2940
|
this._stateService.save(this.stateKey, {
|
|
2747
|
-
page: this.gridRef?.currentPage ?? 1,
|
|
2748
|
-
pageSize: this.gridRef?.pageSize ?? this.pageSize,
|
|
2941
|
+
page: this.viewMode === 'card' ? this.cardPage : (this.gridRef?.currentPage ?? 1),
|
|
2942
|
+
pageSize: this.viewMode === 'card' ? this._effectiveCardPageSize : (this.gridRef?.pageSize ?? this.pageSize),
|
|
2749
2943
|
searchValue: this.searchbar?.searchControl?.value ?? '',
|
|
2750
|
-
scrollTop: this.
|
|
2944
|
+
scrollTop: this.viewMode === 'card'
|
|
2945
|
+
? (this.cardContainerRef?.nativeElement?.scrollTop ?? 0)
|
|
2946
|
+
: (this.gridRef?.getScrollTop() ?? 0),
|
|
2751
2947
|
highlightedRow: row,
|
|
2752
2948
|
});
|
|
2753
2949
|
}
|
|
@@ -2758,7 +2954,6 @@ class OslSetup {
|
|
|
2758
2954
|
}
|
|
2759
2955
|
this._openDialog();
|
|
2760
2956
|
if (this.beforeDisplay) {
|
|
2761
|
-
// this.datasource.find(x=>x[this.primaryKey]==)
|
|
2762
2957
|
this.formLoading = true;
|
|
2763
2958
|
this.dialogModel = await this.beforeDisplay(row);
|
|
2764
2959
|
this.formLoading = false;
|
|
@@ -2810,13 +3005,16 @@ class OslSetup {
|
|
|
2810
3005
|
formFooter: this.customFormFooter ? this.customFooterWrapperTpl : this.formFooterTpl,
|
|
2811
3006
|
},
|
|
2812
3007
|
});
|
|
3008
|
+
this._dialogRef.afterClosed().subscribe(() => {
|
|
3009
|
+
this.statemainTain();
|
|
3010
|
+
});
|
|
2813
3011
|
}
|
|
2814
3012
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2815
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave" }, outputs: { onSearch: "onSearch", onAdd: "onAdd", onEdit: "onEdit", onDelete: "onDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", onRowClick: "onRowClick", onStateRestored: "onStateRestored" }, viewQueries: [{ propertyName: "formBodyTpl", first: true, predicate: ["formBodyTpl"], descendants: true }, { propertyName: "formFooterTpl", first: true, predicate: ["formFooterTpl"], descendants: true }, { propertyName: "customFooterWrapperTpl", first: true, predicate: ["customFooterWrapperTpl"], descendants: true }, { propertyName: "searchbar", first: true, predicate: ["searchbar"], descendants: true }, { propertyName: "gridRef", first: true, predicate: ["gridRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"p-2\">\r\n\r\n <!-- Header -->\r\n <div class=\"osl-setup-header\">\r\n <h5 class=\"mb-0\">{{ title }}</h5>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\r\n @if(!isLister && canAdd){\r\n <osl-button\r\n variant=\"secondary\"\r\n size=\"sm\"\r\n [label]=\"'Add ' + title\"\r\n (clickEv)=\"openAddDialog()\"\r\n ></osl-button>\r\n\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Grid -->\r\n <div class=\"osl-setup-body my-2\">\r\n <osl-grid\r\n #gridRef\r\n [columns]=\"columnsWithActions\"\r\n [(datasource)]=\"datasource\"\r\n [isPaginated]=\"isPaginated\"\r\n [pageSize]=\"pageSize\"\r\n [autoMode]=\"autoMode\"\r\n [tableHeight]=\"tableHeight\"\r\n [totalRecords]=\"totalRecords\"\r\n [loading]=\"loading\"\r\n [moreMenuActions]=\"moreMenuActions\"\r\n [canEdit]=\"canEdit\"\r\n [canDelete]=\"canDelete\"\r\n [highlightedRow]=\"restoredRow\"\r\n [primaryKey]=\"primaryKey\"\r\n (editClick)=\"openEditDialog($event)\"\r\n (deleteClick)=\"onDeleteClick($event)\"\r\n (pageChange)=\"onPageChange(pageChange,$event)\"\r\n (pageSizeChange)=\"onPageChange(pageSizeChange,$event)\"\r\n (sortChange)=\"sortChange.emit($event)\"\r\n [isSelectable]=\"isLister\";\r\n (onRowClick)=\"onRowClick.emit($event)\"\r\n />\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Dialog: Form Body -->\r\n<ng-template #formBodyTpl>\r\n <osl-dynamic-form\r\n [skeletonLoading]=\"formLoading\"\r\n [elements]=\"formElements\"\r\n [(model)]=\"dialogModel\"\r\n ></osl-dynamic-form>\r\n</ng-template>\r\n\r\n<!-- Dialog: Form Footer -->\r\n<ng-template #formFooterTpl>\r\n <div class=\"osl-setup-dialog-footer\">\r\n\r\n\r\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\r\n\r\n </div>\r\n</ng-template>\r\n\r\n<!-- Wrapper that bridges DialogWrapper's implicit context to the custom footer template -->\r\n<ng-template #customFooterWrapperTpl let-data>\r\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\r\n</ng-template>\r\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }, { kind: "component", type: OslSearchbar, selector: "osl-searchbar", inputs: ["label"], outputs: ["onSearch"] }, { kind: "component", type: OslGrid, selector: "osl-grid", inputs: ["columns", "datasource", "isPaginated", "pageSize", "autoMode", "totalRecords", "tableHeight", "loading", "isSelectable", "moreMenuActions", "canEdit", "canDelete", "highlightedRow", "primaryKey"], outputs: ["datasourceChange", "pageChange", "pageSizeChange", "sortChange", "editClick", "deleteClick", "onRowClick"] }] });
|
|
3013
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslSetup, isStandalone: false, selector: "osl-setup", inputs: { title: "title", columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", autoMode: "autoMode", tableHeight: "tableHeight", totalRecords: "totalRecords", loading: "loading", dialogWidth: "dialogWidth", formElements: "formElements", beforeDisplay: "beforeDisplay", onAddEditFn: "onAddEditFn", isLister: "isLister", canAdd: "canAdd", canEdit: "canEdit", canDelete: "canDelete", moreMenuActions: "moreMenuActions", customFormFooter: "customFormFooter", customHeaderTemp: "customHeaderTemp", partialCustomHeaderTemp: "partialCustomHeaderTemp", stateKey: "stateKey", primaryKey: "primaryKey", onSave: "onSave", cardPageSize: "cardPageSize", cardTemplate: "cardTemplate" }, outputs: { onSearch: "onSearch", onAdd: "onAdd", onEdit: "onEdit", onDelete: "onDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", onRowClick: "onRowClick", onStateRestored: "onStateRestored" }, host: { listeners: { "document:click": "onDocumentClick()" } }, viewQueries: [{ propertyName: "formBodyTpl", first: true, predicate: ["formBodyTpl"], descendants: true }, { propertyName: "formFooterTpl", first: true, predicate: ["formFooterTpl"], descendants: true }, { propertyName: "customFooterWrapperTpl", first: true, predicate: ["customFooterWrapperTpl"], descendants: true }, { propertyName: "searchbar", first: true, predicate: ["searchbar"], descendants: true }, { propertyName: "gridRef", first: true, predicate: ["gridRef"], descendants: true }, { propertyName: "cardContainerRef", first: true, predicate: ["cardContainerRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"p-2\">\n\n <!-- Header -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n <div class=\"osl-view-toggle\">\n <button\n class=\"osl-view-toggle-btn\"\n [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\"\n title=\"Table view\">\n <mat-icon>table_rows</mat-icon>\n </button>\n <button\n class=\"osl-view-toggle-btn\"\n [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\"\n title=\"Card view\">\n <mat-icon>grid_view</mat-icon>\n </button>\n </div>\n\n @if (!isLister && canAdd) {\n <osl-button\n variant=\"secondary\"\n size=\"sm\"\n [label]=\"'Add ' + title\"\n (clickEv)=\"openAddDialog()\"\n ></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div\n #cardContainerRef\n class=\"osl-card-container my-2\"\n [style.height]=\"tableHeight\"\n (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton: initial loading with no data yet -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-header\">\n <div class=\"osl-skeleton osl-skeleton--card-title\"></div>\n </div>\n <div class=\"osl-card-body\">\n <div class=\"osl-skeleton osl-skeleton--card-field\"></div>\n <div class=\"osl-skeleton osl-skeleton--card-field\"></div>\n <div class=\"osl-skeleton osl-skeleton--card-field\"></div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <div class=\"d-flex align-items-center justify-content-center floating-element\">\n <div class=\"mx-4\">\n <p class=\"f-s-20 f-w-600 text-start\">No Records match the <br> current filters.</p>\n <p class=\"text-start\">Expecting to see new Records? Try again in <br> a few seconds as the system catches up</p>\n </div>\n <i class=\"icon\"></i>\n </div>\n </div>\n }\n\n <!-- Cards -->\n @else {\n <div class=\"osl-card-grid\">\n\n <!-- Custom card template -->\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n }\n\n <!-- Auto-generated card -->\n @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.pointer]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <!-- Card header: title + action buttons -->\n <div class=\"osl-card-header\">\n @if (cardTitleColumn) {\n <span class=\"osl-card-title\">{{ getCellValue(row, cardTitleColumn) }}</span>\n }\n\n @if (!isLister && (canEdit || canDelete || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions dropdown -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <div class=\"osl-menu-wrapper\">\n <button class=\"osl-grid-icon-btn\" (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <mat-icon>more_vert</mat-icon>\n </button>\n @if (cardOpenMenuIndex === i) {\n <div class=\"osl-custom-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\">\n <div class=\"osl-custom-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(row)) {\n <button class=\"osl-custom-menu-item\"\n (click)=\"action.click(row); cardOpenMenuIndex = null; $event.stopPropagation()\">\n <span class=\"osl-custom-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(row) : action.label }}\n </button>\n }\n }\n </div>\n }\n </div>\n }\n\n @if (canEdit) {\n <button class=\"osl-grid-icon-btn\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\"\n title=\"Edit\">\n <mat-icon>mode_edit_outline</mat-icon>\n </button>\n }\n @if (canDelete) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\"\n title=\"Delete\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Card body: remaining column values -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\">{{ getCellValue(row, col) }}</span>\n </div>\n }\n </div>\n\n </div>\n }\n }\n </div>\n\n <!-- Bottom spinner while loading next page -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-spinner\"></div>\n </div>\n }\n\n <!-- All records loaded indicator -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n All {{ totalRecords || cardDatasource.length }} records loaded\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<!-- Wrapper that bridges DialogWrapper's implicit context to the custom footer template -->\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-view-toggle{display:flex;border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);overflow:hidden;flex-shrink:0}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:transparent;cursor:pointer;color:#6b7280;transition:background .12s,color .12s;padding:0}.osl-view-toggle-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.osl-view-toggle-btn:first-child{border-right:1px solid var(--osl-border-color)}.osl-view-toggle-btn--active{background:var(--osl-primary);color:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6}.osl-card-container{overflow-y:auto;overflow-x:hidden;border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);background:#f9fafb;padding:16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}.osl-card{background:#fff;border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);padding:16px;transition:box-shadow .15s,border-color .15s;display:flex;flex-direction:column}.osl-card:hover{box-shadow:0 4px 12px #00000014;border-color:#9ca3af}.osl-card--highlighted{box-shadow:inset 4px 0 0 var(--osl-primary)!important;animation:osl-card-highlight-fade 3s ease-out forwards}.osl-card--skeleton{pointer-events:none}.osl-card--skeleton:hover{box-shadow:none;border-color:var(--osl-border-color)}@keyframes osl-card-highlight-fade{0%,60%{background:#6366f10d}to{background:#fff}}.osl-card-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid #f3f4f6}.osl-card-title{font-weight:600;font-size:14px;color:#111827;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.osl-card-body{display:flex;flex-direction:column;gap:8px;flex:1}.osl-card-field{display:flex;align-items:baseline;gap:8px;font-size:13px;line-height:1.4}.osl-card-label{color:#6b7280;font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.04em;min-width:90px;flex-shrink:0}.osl-card-value{color:#111827;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-card-empty{display:flex;align-items:center;justify-content:center;min-height:300px;height:100%}.osl-card-loader{display:flex;justify-content:center;align-items:center;padding:24px}.osl-card-loader-spinner{width:32px;height:32px;border:3px solid #e5e7eb;border-top-color:var(--osl-primary);border-radius:50%;animation:osl-spin .8s linear infinite}@keyframes osl-spin{to{transform:rotate(360deg)}}.osl-card-all-loaded{text-align:center;padding:16px;font-size:12px;color:#9ca3af;border-top:1px solid #f3f4f6;margin-top:8px}.osl-skeleton--card-title{height:16px;width:65%;background:#e5e7eb;border-radius:4px;animation:osl-skeleton-pulse 1.4s ease-in-out infinite}.osl-skeleton--card-field{height:12px;width:85%;background:#e5e7eb;border-radius:4px;animation:osl-skeleton-pulse 1.4s ease-in-out infinite}@keyframes osl-skeleton-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DynamicForm, selector: "osl-dynamic-form", inputs: ["elements", "model", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }, { kind: "component", type: OslSearchbar, selector: "osl-searchbar", inputs: ["label"], outputs: ["onSearch"] }, { kind: "component", type: OslGrid, selector: "osl-grid", inputs: ["columns", "datasource", "isPaginated", "pageSize", "autoMode", "totalRecords", "tableHeight", "loading", "isSelectable", "moreMenuActions", "canEdit", "canDelete", "highlightedRow", "primaryKey"], outputs: ["datasourceChange", "pageChange", "pageSizeChange", "sortChange", "editClick", "deleteClick", "onRowClick"] }] });
|
|
2816
3014
|
}
|
|
2817
3015
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslSetup, decorators: [{
|
|
2818
3016
|
type: Component,
|
|
2819
|
-
args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\r\n\r\n <!-- Header -->\r\n <div class=\"osl-setup-header\">\r\n <h5 class=\"mb-0\">{{ title }}</h5>\r\n <div class=\"d-flex align-items-center gap-2\">\r\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\r\n @if(!isLister && canAdd){\r\n <osl-button\r\n variant=\"secondary\"\r\n size=\"sm\"\r\n [label]=\"'Add ' + title\"\r\n (clickEv)=\"openAddDialog()\"\r\n ></osl-button>\r\n\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- Grid -->\r\n <div class=\"osl-setup-body my-2\">\r\n <osl-grid\r\n #gridRef\r\n [columns]=\"columnsWithActions\"\r\n [(datasource)]=\"datasource\"\r\n [isPaginated]=\"isPaginated\"\r\n [pageSize]=\"pageSize\"\r\n [autoMode]=\"autoMode\"\r\n [tableHeight]=\"tableHeight\"\r\n [totalRecords]=\"totalRecords\"\r\n [loading]=\"loading\"\r\n [moreMenuActions]=\"moreMenuActions\"\r\n [canEdit]=\"canEdit\"\r\n [canDelete]=\"canDelete\"\r\n [highlightedRow]=\"restoredRow\"\r\n [primaryKey]=\"primaryKey\"\r\n (editClick)=\"openEditDialog($event)\"\r\n (deleteClick)=\"onDeleteClick($event)\"\r\n (pageChange)=\"onPageChange(pageChange,$event)\"\r\n (pageSizeChange)=\"onPageChange(pageSizeChange,$event)\"\r\n (sortChange)=\"sortChange.emit($event)\"\r\n [isSelectable]=\"isLister\";\r\n (onRowClick)=\"onRowClick.emit($event)\"\r\n />\r\n </div>\r\n\r\n</div>\r\n\r\n<!-- Dialog: Form Body -->\r\n<ng-template #formBodyTpl>\r\n <osl-dynamic-form\r\n [skeletonLoading]=\"formLoading\"\r\n [elements]=\"formElements\"\r\n [(model)]=\"dialogModel\"\r\n ></osl-dynamic-form>\r\n</ng-template>\r\n\r\n<!-- Dialog: Form Footer -->\r\n<ng-template #formFooterTpl>\r\n <div class=\"osl-setup-dialog-footer\">\r\n\r\n\r\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\r\n\r\n </div>\r\n</ng-template>\r\n\r\n<!-- Wrapper that bridges DialogWrapper's implicit context to the custom footer template -->\r\n<ng-template #customFooterWrapperTpl let-data>\r\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\r\n</ng-template>\r\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}\n"] }]
|
|
3017
|
+
args: [{ selector: 'osl-setup', standalone: false, template: "<div class=\"p-2\">\n\n <!-- Header -->\n <div class=\"osl-setup-header\">\n <h5 class=\"mb-0\">{{ title }}</h5>\n <div class=\"d-flex align-items-center gap-2\">\n <osl-searchbar #searchbar class=\"mx-2\" (onSearch)=\"onSearchSetup($event)\"></osl-searchbar>\n\n <!-- View Toggle -->\n <div class=\"osl-view-toggle\">\n <button\n class=\"osl-view-toggle-btn\"\n [class.osl-view-toggle-btn--active]=\"viewMode === 'table'\"\n (click)=\"toggleView('table')\"\n title=\"Table view\">\n <mat-icon>table_rows</mat-icon>\n </button>\n <button\n class=\"osl-view-toggle-btn\"\n [class.osl-view-toggle-btn--active]=\"viewMode === 'card'\"\n (click)=\"toggleView('card')\"\n title=\"Card view\">\n <mat-icon>grid_view</mat-icon>\n </button>\n </div>\n\n @if (!isLister && canAdd) {\n <osl-button\n variant=\"secondary\"\n size=\"sm\"\n [label]=\"'Add ' + title\"\n (clickEv)=\"openAddDialog()\"\n ></osl-button>\n }\n </div>\n </div>\n\n <!-- \u2500\u2500 Table View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'table') {\n <div class=\"osl-setup-body my-2\">\n <osl-grid\n #gridRef\n [columns]=\"columnsWithActions\"\n [(datasource)]=\"datasource\"\n [isPaginated]=\"isPaginated\"\n [pageSize]=\"pageSize\"\n [autoMode]=\"autoMode\"\n [tableHeight]=\"tableHeight\"\n [totalRecords]=\"totalRecords\"\n [loading]=\"loading\"\n [moreMenuActions]=\"moreMenuActions\"\n [canEdit]=\"canEdit\"\n [canDelete]=\"canDelete\"\n [highlightedRow]=\"restoredRow\"\n [primaryKey]=\"primaryKey\"\n (editClick)=\"openEditDialog($event)\"\n (deleteClick)=\"onDeleteClick($event)\"\n (pageChange)=\"onPageChange(pageChange, $event)\"\n (pageSizeChange)=\"onPageChange(pageSizeChange, $event)\"\n (sortChange)=\"sortChange.emit($event)\"\n [isSelectable]=\"isLister\"\n (onRowClick)=\"onRowClick.emit($event)\"\n />\n </div>\n }\n\n <!-- \u2500\u2500 Card View \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n @if (viewMode === 'card') {\n <div\n #cardContainerRef\n class=\"osl-card-container my-2\"\n [style.height]=\"tableHeight\"\n (scroll)=\"onCardScroll($event)\">\n\n <!-- Skeleton: initial loading with no data yet -->\n @if (loading && cardDatasource.length === 0) {\n <div class=\"osl-card-grid\">\n @for (sk of skeletonCardRows; track $index) {\n <div class=\"osl-card osl-card--skeleton\">\n <div class=\"osl-card-header\">\n <div class=\"osl-skeleton osl-skeleton--card-title\"></div>\n </div>\n <div class=\"osl-card-body\">\n <div class=\"osl-skeleton osl-skeleton--card-field\"></div>\n <div class=\"osl-skeleton osl-skeleton--card-field\"></div>\n <div class=\"osl-skeleton osl-skeleton--card-field\"></div>\n </div>\n </div>\n }\n </div>\n }\n\n <!-- Empty state -->\n @else if (cardDatasource.length === 0 && !loading) {\n <div class=\"osl-card-empty\">\n <div class=\"d-flex align-items-center justify-content-center floating-element\">\n <div class=\"mx-4\">\n <p class=\"f-s-20 f-w-600 text-start\">No Records match the <br> current filters.</p>\n <p class=\"text-start\">Expecting to see new Records? Try again in <br> a few seconds as the system catches up</p>\n </div>\n <i class=\"icon\"></i>\n </div>\n </div>\n }\n\n <!-- Cards -->\n @else {\n <div class=\"osl-card-grid\">\n\n <!-- Custom card template -->\n @if (cardTemplate) {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <ng-container *ngTemplateOutlet=\"cardTemplate; context: { $implicit: row, index: i }\"></ng-container>\n }\n }\n\n <!-- Auto-generated card -->\n @else {\n @for (row of cardDatasource; track row[primaryKey]; let i = $index) {\n <div class=\"osl-card\"\n [class.osl-card--highlighted]=\"isHighlightedCard(row)\"\n [class.pointer]=\"isLister\"\n (click)=\"isLister ? onRowClick.emit(row) : null\">\n\n <!-- Card header: title + action buttons -->\n <div class=\"osl-card-header\">\n @if (cardTitleColumn) {\n <span class=\"osl-card-title\">{{ getCellValue(row, cardTitleColumn) }}</span>\n }\n\n @if (!isLister && (canEdit || canDelete || (moreMenuActions.length > 0 && hasVisibleActions(row)))) {\n <div class=\"osl-card-actions\">\n\n <!-- More-actions dropdown -->\n @if (moreMenuActions.length > 0 && hasVisibleActions(row)) {\n <div class=\"osl-menu-wrapper\">\n <button class=\"osl-grid-icon-btn\" (click)=\"toggleCardMenu(i, $event)\" title=\"More actions\">\n <mat-icon>more_vert</mat-icon>\n </button>\n @if (cardOpenMenuIndex === i) {\n <div class=\"osl-custom-menu\"\n [style.top.px]=\"cardMenuPosition.top\"\n [style.left.px]=\"cardMenuPosition.left\">\n <div class=\"osl-custom-menu-header\">Actions</div>\n @for (action of moreMenuActions; track $index) {\n @if (!action.hideIf || !action.hideIf(row)) {\n <button class=\"osl-custom-menu-item\"\n (click)=\"action.click(row); cardOpenMenuIndex = null; $event.stopPropagation()\">\n <span class=\"osl-custom-menu-dot\"></span>\n {{ action.labelIf ? action.labelIf(row) : action.label }}\n </button>\n }\n }\n </div>\n }\n </div>\n }\n\n @if (canEdit) {\n <button class=\"osl-grid-icon-btn\"\n (click)=\"$event.stopPropagation(); openEditDialog(row)\"\n title=\"Edit\">\n <mat-icon>mode_edit_outline</mat-icon>\n </button>\n }\n @if (canDelete) {\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\"\n (click)=\"$event.stopPropagation(); onDeleteClick(row)\"\n title=\"Delete\">\n <mat-icon>delete_outline</mat-icon>\n </button>\n }\n </div>\n }\n </div>\n\n <!-- Card body: remaining column values -->\n <div class=\"osl-card-body\">\n @for (col of cardBodyColumns; track col.key) {\n <div class=\"osl-card-field\">\n <span class=\"osl-card-label\">{{ col.label }}</span>\n <span class=\"osl-card-value\">{{ getCellValue(row, col) }}</span>\n </div>\n }\n </div>\n\n </div>\n }\n }\n </div>\n\n <!-- Bottom spinner while loading next page -->\n @if (loading) {\n <div class=\"osl-card-loader\">\n <div class=\"osl-card-loader-spinner\"></div>\n </div>\n }\n\n <!-- All records loaded indicator -->\n @if (allCardsLoaded && !loading && cardDatasource.length > 0) {\n <div class=\"osl-card-all-loaded\">\n All {{ totalRecords || cardDatasource.length }} records loaded\n </div>\n }\n }\n </div>\n }\n\n</div>\n\n<!-- Dialog: Form Body -->\n<ng-template #formBodyTpl>\n <osl-dynamic-form\n [skeletonLoading]=\"formLoading\"\n [elements]=\"formElements\"\n [(model)]=\"dialogModel\"\n ></osl-dynamic-form>\n</ng-template>\n\n<!-- Dialog: Form Footer -->\n<ng-template #formFooterTpl>\n <div class=\"osl-setup-dialog-footer\">\n <osl-button [loading]=\"saveLoading\" variant=\"secondary\" label=\"Save\" (click)=\"saveDialog()\"></osl-button>\n </div>\n</ng-template>\n\n<!-- Wrapper that bridges DialogWrapper's implicit context to the custom footer template -->\n<ng-template #customFooterWrapperTpl let-data>\n <ng-container *ngTemplateOutlet=\"customFormFooter!; context: { $implicit: { dialogModel: dialogModel, dialogMode: dialogMode, dialogRef: data.dialogRef } }\"></ng-container>\n</ng-template>\n", styles: [".osl-setup-header{display:flex;align-items:center;justify-content:space-between}.osl-view-toggle{display:flex;border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);overflow:hidden;flex-shrink:0}.osl-view-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;background:transparent;cursor:pointer;color:#6b7280;transition:background .12s,color .12s;padding:0}.osl-view-toggle-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.osl-view-toggle-btn:first-child{border-right:1px solid var(--osl-border-color)}.osl-view-toggle-btn--active{background:var(--osl-primary);color:#fff}.osl-view-toggle-btn:not(.osl-view-toggle-btn--active):hover{background:#f3f4f6}.osl-card-container{overflow-y:auto;overflow-x:hidden;border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);background:#f9fafb;padding:16px;box-sizing:border-box}.osl-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}.osl-card{background:#fff;border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);padding:16px;transition:box-shadow .15s,border-color .15s;display:flex;flex-direction:column}.osl-card:hover{box-shadow:0 4px 12px #00000014;border-color:#9ca3af}.osl-card--highlighted{box-shadow:inset 4px 0 0 var(--osl-primary)!important;animation:osl-card-highlight-fade 3s ease-out forwards}.osl-card--skeleton{pointer-events:none}.osl-card--skeleton:hover{box-shadow:none;border-color:var(--osl-border-color)}@keyframes osl-card-highlight-fade{0%,60%{background:#6366f10d}to{background:#fff}}.osl-card-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px;padding-bottom:10px;border-bottom:1px solid #f3f4f6}.osl-card-title{font-weight:600;font-size:14px;color:#111827;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.osl-card-body{display:flex;flex-direction:column;gap:8px;flex:1}.osl-card-field{display:flex;align-items:baseline;gap:8px;font-size:13px;line-height:1.4}.osl-card-label{color:#6b7280;font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:.04em;min-width:90px;flex-shrink:0}.osl-card-value{color:#111827;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-card-empty{display:flex;align-items:center;justify-content:center;min-height:300px;height:100%}.osl-card-loader{display:flex;justify-content:center;align-items:center;padding:24px}.osl-card-loader-spinner{width:32px;height:32px;border:3px solid #e5e7eb;border-top-color:var(--osl-primary);border-radius:50%;animation:osl-spin .8s linear infinite}@keyframes osl-spin{to{transform:rotate(360deg)}}.osl-card-all-loaded{text-align:center;padding:16px;font-size:12px;color:#9ca3af;border-top:1px solid #f3f4f6;margin-top:8px}.osl-skeleton--card-title{height:16px;width:65%;background:#e5e7eb;border-radius:4px;animation:osl-skeleton-pulse 1.4s ease-in-out infinite}.osl-skeleton--card-field{height:12px;width:85%;background:#e5e7eb;border-radius:4px;animation:osl-skeleton-pulse 1.4s ease-in-out infinite}@keyframes osl-skeleton-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-setup-dialog-footer{display:flex;align-items:center;justify-content:flex-end;gap:10px;width:100%}.dialog-cancel-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;border-color:var(--osl-border-color, #d1d5db);color:var(--osl-secondary, #6b7280);border-radius:var(--osl-border-radius, 4px);padding:0 16px;transition:all .2s ease}.dialog-cancel-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-cancel-btn:hover{border-color:#9ca3af;background:#f9fafb;color:#374151}.dialog-save-btn{display:flex;align-items:center;gap:6px;height:38px;font-size:13px;font-weight:500;background:linear-gradient(135deg,var(--osl-primary, #2563eb),#3b82f6);color:#fff;border-radius:var(--osl-border-radius, 4px);padding:0 18px;transition:all .2s ease;box-shadow:0 2px 8px #2563eb40}.dialog-save-btn mat-icon{font-size:17px;width:17px;height:17px}.dialog-save-btn:hover{background:linear-gradient(135deg,var(--osl-primary-hover, #1d4ed8),#2563eb);box-shadow:0 4px 14px #2563eb66;transform:translateY(-1px)}.dialog-save-btn:active{transform:translateY(0);box-shadow:0 2px 8px #2563eb40}\n"] }]
|
|
2820
3018
|
}], propDecorators: { formBodyTpl: [{
|
|
2821
3019
|
type: ViewChild,
|
|
2822
3020
|
args: ['formBodyTpl']
|
|
@@ -2829,6 +3027,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2829
3027
|
}], searchbar: [{
|
|
2830
3028
|
type: ViewChild,
|
|
2831
3029
|
args: ['searchbar']
|
|
3030
|
+
}], gridRef: [{
|
|
3031
|
+
type: ViewChild,
|
|
3032
|
+
args: ['gridRef']
|
|
3033
|
+
}], cardContainerRef: [{
|
|
3034
|
+
type: ViewChild,
|
|
3035
|
+
args: ['cardContainerRef']
|
|
2832
3036
|
}], title: [{
|
|
2833
3037
|
type: Input,
|
|
2834
3038
|
args: ['title']
|
|
@@ -2898,6 +3102,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2898
3102
|
}], primaryKey: [{
|
|
2899
3103
|
type: Input,
|
|
2900
3104
|
args: ['primaryKey']
|
|
3105
|
+
}], onSave: [{
|
|
3106
|
+
type: Input,
|
|
3107
|
+
args: ['onSave']
|
|
3108
|
+
}], cardPageSize: [{
|
|
3109
|
+
type: Input,
|
|
3110
|
+
args: ['cardPageSize']
|
|
3111
|
+
}], cardTemplate: [{
|
|
3112
|
+
type: Input,
|
|
3113
|
+
args: ['cardTemplate']
|
|
2901
3114
|
}], onSearch: [{
|
|
2902
3115
|
type: Output
|
|
2903
3116
|
}], onAdd: [{
|
|
@@ -2916,12 +3129,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2916
3129
|
type: Output
|
|
2917
3130
|
}], onStateRestored: [{
|
|
2918
3131
|
type: Output
|
|
2919
|
-
}],
|
|
2920
|
-
type:
|
|
2921
|
-
args: ['
|
|
2922
|
-
}], onSave: [{
|
|
2923
|
-
type: Input,
|
|
2924
|
-
args: ['onSave']
|
|
3132
|
+
}], onDocumentClick: [{
|
|
3133
|
+
type: HostListener,
|
|
3134
|
+
args: ['document:click']
|
|
2925
3135
|
}] } });
|
|
2926
3136
|
|
|
2927
3137
|
class OslFormGrid {
|
|
@@ -3142,7 +3352,7 @@ class OslAutocompleteLister {
|
|
|
3142
3352
|
this.dialogRef.close(event);
|
|
3143
3353
|
}
|
|
3144
3354
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslAutocompleteLister, deps: [{ token: i1.MatDialogRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3145
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: OslAutocompleteLister, isStandalone: false, selector: "osl-autocomplete-lister", inputs: { data: "data" }, ngImport: i0, template: "<div class=\"p-4\">\r\n <osl-setup [loading]=\"loader\" [autoMode]=\"false\" [title]=\"autocompleteData?.title\" [totalRecords]=\"recordCount\" (pageSizeChange)=\"onPageChange($event)\" (pageChange)=\"onPageChange($event)\" (onRowClick)=\"onRowClick($event)\" [columns]=\"column\" [datasource]=\"datasource\" [isLister]=\"true\"></osl-setup>\r\n \r\n</div>", styles: [""], dependencies: [{ kind: "component", type: OslSetup, selector: "osl-setup", inputs: ["title", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
|
|
3355
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: OslAutocompleteLister, isStandalone: false, selector: "osl-autocomplete-lister", inputs: { data: "data" }, ngImport: i0, template: "<div class=\"p-4\">\r\n <osl-setup [loading]=\"loader\" [autoMode]=\"false\" [title]=\"autocompleteData?.title\" [totalRecords]=\"recordCount\" (pageSizeChange)=\"onPageChange($event)\" (pageChange)=\"onPageChange($event)\" (onRowClick)=\"onRowClick($event)\" [columns]=\"column\" [datasource]=\"datasource\" [isLister]=\"true\"></osl-setup>\r\n \r\n</div>", styles: [""], dependencies: [{ kind: "component", type: OslSetup, selector: "osl-setup", inputs: ["title", "columns", "datasource", "isPaginated", "pageSize", "autoMode", "tableHeight", "totalRecords", "loading", "dialogWidth", "formElements", "beforeDisplay", "onAddEditFn", "isLister", "canAdd", "canEdit", "canDelete", "moreMenuActions", "customFormFooter", "customHeaderTemp", "partialCustomHeaderTemp", "stateKey", "primaryKey", "onSave", "cardPageSize", "cardTemplate"], outputs: ["onSearch", "onAdd", "onEdit", "onDelete", "pageChange", "pageSizeChange", "sortChange", "onRowClick", "onStateRestored"] }] });
|
|
3146
3356
|
}
|
|
3147
3357
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslAutocompleteLister, decorators: [{
|
|
3148
3358
|
type: Component,
|