osl-base-extended 1.1.23 → 1.1.25
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,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Component, Inject, inject, Injector, Injectable, InjectionToken, PLATFORM_ID, Input, Optional, Directive, EventEmitter, Output, HostListener, ViewChild, NgModule } from '@angular/core';
|
|
2
|
+
import { Component, Inject, inject, Injector, Injectable, InjectionToken, PLATFORM_ID, Input, Optional, Directive, EventEmitter, Output, HostListener, ViewChild, NgModule, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/material/dialog';
|
|
4
4
|
import { MAT_DIALOG_DATA, MatDialogModule, MatDialog } from '@angular/material/dialog';
|
|
5
5
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
6
6
|
import { Router } from '@angular/router';
|
|
7
7
|
import * as i1$2 from '@angular/common';
|
|
8
|
-
import { NgTemplateOutlet, NgComponentOutlet, isPlatformBrowser, NgStyle,
|
|
8
|
+
import { NgTemplateOutlet, NgComponentOutlet, isPlatformBrowser, DatePipe, DecimalPipe, NgStyle, NgClass, UpperCasePipe } from '@angular/common';
|
|
9
9
|
import * as i3 from '@angular/material/button';
|
|
10
10
|
import { MatIconButton, MatButtonModule } from '@angular/material/button';
|
|
11
11
|
import * as i2 from '@angular/material/icon';
|
|
@@ -21,6 +21,9 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
|
21
21
|
import { MatInputModule } from '@angular/material/input';
|
|
22
22
|
import { debounceTime as debounceTime$1, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs/operators';
|
|
23
23
|
import { MatMenuModule } from '@angular/material/menu';
|
|
24
|
+
import { ScrollingModule } from '@angular/cdk/scrolling';
|
|
25
|
+
import * as i3$1 from '@angular/cdk/drag-drop';
|
|
26
|
+
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
24
27
|
|
|
25
28
|
class OslBaseExtended {
|
|
26
29
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslBaseExtended, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -170,7 +173,7 @@ class baseComponent {
|
|
|
170
173
|
continue;
|
|
171
174
|
}
|
|
172
175
|
if (el.inputType === 'email' && !isEmpty) {
|
|
173
|
-
const emailRegex = /^[
|
|
176
|
+
const emailRegex = /^(?!.*\.\.)(?!.*\.$)[A-Za-z0-9][A-Za-z0-9._%+-]{0,63}@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
174
177
|
if (!emailRegex.test(value)) {
|
|
175
178
|
errors.push(`${el.label} must be a valid email address`);
|
|
176
179
|
}
|
|
@@ -960,6 +963,7 @@ class Oslinput {
|
|
|
960
963
|
skeletonLoading = false;
|
|
961
964
|
skeletonTheme = 'light';
|
|
962
965
|
onlyChars = false;
|
|
966
|
+
isCapitalize = false;
|
|
963
967
|
/** Enforce N decimal places. Shows 0.00 when blank; auto-pads on blur. */
|
|
964
968
|
decimalPortion = null;
|
|
965
969
|
modelChange = new EventEmitter();
|
|
@@ -1102,6 +1106,9 @@ class Oslinput {
|
|
|
1102
1106
|
this.modelChange.emit(this.model);
|
|
1103
1107
|
return;
|
|
1104
1108
|
}
|
|
1109
|
+
if (this.isCapitalize) {
|
|
1110
|
+
value = typeof value == 'string' ? value?.toUpperCase() : value;
|
|
1111
|
+
}
|
|
1105
1112
|
const processed = this.mask ? this.applyMask(value) : value;
|
|
1106
1113
|
this.model = processed;
|
|
1107
1114
|
this.modelChange.emit(this.model);
|
|
@@ -1141,7 +1148,7 @@ class Oslinput {
|
|
|
1141
1148
|
return result;
|
|
1142
1149
|
}
|
|
1143
1150
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: Oslinput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1144
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: Oslinput, isStandalone: false, selector: "osl-input", inputs: { label: "label", required: "required", disabled: "disabled", model: "model", type: "type", placeholder: "placeholder", mask: "mask", min: "min", max: "max", minLength: "minLength", maxLength: "maxLength", prefixIcon: "prefixIcon", suffixIcon: "suffixIcon", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme", onlyChars: "onlyChars", decimalPortion: "decimalPortion" }, outputs: { modelChange: "modelChange", changeEv: "changeEv" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"d-flex flex-column\">\r\n @if(label){\r\n <div [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <label class=\"label\" [class.txt-clr-red]=\"myField.touched && myField.invalid\">\r\n <span [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">{{label}} <span class=\"txt-clr-red\">{{required?'*':''}}</span></span>\r\n </label>\r\n </div>\r\n}\r\n\r\n <div class=\"input-wrapper\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\" [class.plain]=\"!hasWrapper\" [class.error]=\"myField.touched && myField.invalid\" [class.input-disabled]=\"disabled\">\r\n @if(prefixIcon) {\r\n <mat-icon class=\"input-icon prefix-icon\">{{prefixIcon}}</mat-icon>\r\n }\r\n <input\r\n\r\n [type]=\"inputType\"\r\n [ngModel]=\"model\"\r\n (ngModelChange)=\"onModelChange($event)\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (focus)=\"onFocusIn($event)\"\r\n (blur)=\"onFocusOut()\"\r\n (change)=\"onChange()\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"\r\n [placeholder]=\"placeholder\"\r\n [attr.min]=\"min || null\"\r\n [attr.max]=\"max || null\"\r\n [attr.minlength]=\"minLength\"\r\n [attr.maxlength]=\"maxLength\"\r\n [email]=\"type === 'email'\"\r\n #myField=\"ngModel\"\r\n class=\"inner-input\"\r\n >\r\n @if(type === 'password') {\r\n <button type=\"button\" class=\"password-toggle\" (click)=\"togglePassword()\" tabindex=\"-1\">\r\n <mat-icon>{{showPassword ? 'visibility_off' : 'visibility'}}</mat-icon>\r\n </button>\r\n }\r\n @if(suffixIcon && type !== 'password') {\r\n <mat-icon class=\"input-icon suffix-icon\">{{suffixIcon}}</mat-icon>\r\n }\r\n </div>\r\n\r\n @if(myField.touched && myField.invalid) {\r\n @if(myField.errors?.['required']) {\r\n <mat-hint class=\"hint\">{{label}} is Required!</mat-hint>\r\n } @else if(myField.errors?.['email']) {\r\n <mat-hint class=\"hint\">Please enter a valid email address.</mat-hint>\r\n } @else if(myField.errors?.['minlength']) {\r\n <mat-hint class=\"hint\">Minimum {{minLength}} characters required.</mat-hint>\r\n } @else if(myField.errors?.['maxlength']) {\r\n <mat-hint class=\"hint\">Maximum {{maxLength}} characters allowed.</mat-hint>\r\n } @else if(myField.errors?.['min']) {\r\n <mat-hint class=\"hint\">Value must be at least {{min}}.</mat-hint>\r\n } @else if(myField.errors?.['max']) {\r\n <mat-hint class=\"hint\">Value must be at most {{max}}.</mat-hint>\r\n }\r\n }\r\n</div>\r\n", styles: [".label{font-size:var(--osl-label-font-size);margin-bottom:5px}.input-wrapper{display:flex;align-items:center;height:var(--osl-control-height);width:100%;border-radius:var(--osl-border-radius);border:1px solid var(--osl-border-color);padding:0 8px;gap:6px;transition:border-color .5s}.input-wrapper:focus-within{border-color:var(--osl-focus-border-color)}.input-wrapper.error{border-color:var(--osl-error-color)}.input-wrapper.input-disabled{background:#f5f5f5;opacity:.7;cursor:not-allowed}.input-wrapper.plain{padding:0;border:none;height:auto;gap:0}.input-wrapper.plain .inner-input{border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);padding:5px;height:var(--osl-control-height)}.input-wrapper.plain .inner-input:focus{border-color:var(--osl-focus-border-color)}.input-wrapper.plain .inner-input.error{border-color:var(--osl-error-color)}.inner-input{flex:1;min-width:0;height:100%;border:none;outline:none;font-size:var(--osl-text-font-size);background:transparent;width:100%}.inner-input:disabled{cursor:not-allowed;background:transparent}.inner-input::placeholder{font-size:var(--osl-label-font-size);color:#aaa}.input-icon{font-size:18px;width:18px;height:18px;color:#888;flex-shrink:0;-webkit-user-select:none;user-select:none}.password-toggle{display:flex;align-items:center;justify-content:center;background:none;border:none;padding:0;cursor:pointer;color:#888;flex-shrink:0}.password-toggle mat-icon{font-size:18px;width:18px;height:18px}.password-toggle:hover{color:#333}.hint{color:var(--osl-error-color);margin-top:2px;font-size:var(--osl-hint-font-size)}.txt-clr-red{color:var(--osl-error-color)}\n"], dependencies: [{ kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }] });
|
|
1151
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: Oslinput, isStandalone: false, selector: "osl-input", inputs: { label: "label", required: "required", disabled: "disabled", model: "model", type: "type", placeholder: "placeholder", mask: "mask", min: "min", max: "max", minLength: "minLength", maxLength: "maxLength", prefixIcon: "prefixIcon", suffixIcon: "suffixIcon", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme", onlyChars: "onlyChars", isCapitalize: "isCapitalize", decimalPortion: "decimalPortion" }, outputs: { modelChange: "modelChange", changeEv: "changeEv" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"d-flex flex-column\">\r\n @if(label){\r\n <div [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <label class=\"label\" [class.txt-clr-red]=\"myField.touched && myField.invalid\">\r\n <span [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">{{label}} <span class=\"txt-clr-red\">{{required?'*':''}}</span></span>\r\n </label>\r\n </div>\r\n}\r\n\r\n <div class=\"input-wrapper\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\" [class.plain]=\"!hasWrapper\" [class.error]=\"myField.touched && myField.invalid\" [class.input-disabled]=\"disabled\">\r\n @if(prefixIcon) {\r\n <mat-icon class=\"input-icon prefix-icon\">{{prefixIcon}}</mat-icon>\r\n }\r\n <input\r\n\r\n [type]=\"inputType\"\r\n [ngModel]=\"model\"\r\n (ngModelChange)=\"onModelChange($event)\"\r\n (keydown)=\"onKeyDown($event)\"\r\n (focus)=\"onFocusIn($event)\"\r\n (blur)=\"onFocusOut()\"\r\n (change)=\"onChange()\"\r\n [required]=\"required\"\r\n [disabled]=\"disabled\"\r\n [placeholder]=\"placeholder\"\r\n [attr.min]=\"min || null\"\r\n [attr.max]=\"max || null\"\r\n [attr.minlength]=\"minLength\"\r\n [attr.maxlength]=\"maxLength\"\r\n [email]=\"type === 'email'\"\r\n #myField=\"ngModel\"\r\n class=\"inner-input\"\r\n >\r\n @if(type === 'password') {\r\n <button type=\"button\" class=\"password-toggle\" (click)=\"togglePassword()\" tabindex=\"-1\">\r\n <mat-icon>{{showPassword ? 'visibility_off' : 'visibility'}}</mat-icon>\r\n </button>\r\n }\r\n @if(suffixIcon && type !== 'password') {\r\n <mat-icon class=\"input-icon suffix-icon\">{{suffixIcon}}</mat-icon>\r\n }\r\n </div>\r\n\r\n @if(myField.touched && myField.invalid) {\r\n @if(myField.errors?.['required']) {\r\n <mat-hint class=\"hint\">{{label}} is Required!</mat-hint>\r\n } @else if(myField.errors?.['email']) {\r\n <mat-hint class=\"hint\">Please enter a valid email address.</mat-hint>\r\n } @else if(myField.errors?.['minlength']) {\r\n <mat-hint class=\"hint\">Minimum {{minLength}} characters required.</mat-hint>\r\n } @else if(myField.errors?.['maxlength']) {\r\n <mat-hint class=\"hint\">Maximum {{maxLength}} characters allowed.</mat-hint>\r\n } @else if(myField.errors?.['min']) {\r\n <mat-hint class=\"hint\">Value must be at least {{min}}.</mat-hint>\r\n } @else if(myField.errors?.['max']) {\r\n <mat-hint class=\"hint\">Value must be at most {{max}}.</mat-hint>\r\n }\r\n }\r\n</div>\r\n", styles: [".label{font-size:var(--osl-label-font-size);margin-bottom:5px}.input-wrapper{display:flex;align-items:center;height:var(--osl-control-height);width:100%;border-radius:var(--osl-border-radius);border:1px solid var(--osl-border-color);padding:0 8px;gap:6px;transition:border-color .5s}.input-wrapper:focus-within{border-color:var(--osl-focus-border-color)}.input-wrapper.error{border-color:var(--osl-error-color)}.input-wrapper.input-disabled{background:#f5f5f5;opacity:.7;cursor:not-allowed}.input-wrapper.plain{padding:0;border:none;height:auto;gap:0}.input-wrapper.plain .inner-input{border:1px solid var(--osl-border-color);border-radius:var(--osl-border-radius);padding:5px;height:var(--osl-control-height)}.input-wrapper.plain .inner-input:focus{border-color:var(--osl-focus-border-color)}.input-wrapper.plain .inner-input.error{border-color:var(--osl-error-color)}.inner-input{flex:1;min-width:0;height:100%;border:none;outline:none;font-size:var(--osl-text-font-size);background:transparent;width:100%}.inner-input:disabled{cursor:not-allowed;background:transparent}.inner-input::placeholder{font-size:var(--osl-label-font-size);color:#aaa}.input-icon{font-size:18px;width:18px;height:18px;color:#888;flex-shrink:0;-webkit-user-select:none;user-select:none}.password-toggle{display:flex;align-items:center;justify-content:center;background:none;border:none;padding:0;cursor:pointer;color:#888;flex-shrink:0}.password-toggle mat-icon{font-size:18px;width:18px;height:18px}.password-toggle:hover{color:#333}.hint{color:var(--osl-error-color);margin-top:2px;font-size:var(--osl-hint-font-size)}.txt-clr-red{color:var(--osl-error-color)}\n"], dependencies: [{ kind: "directive", type: i1$1.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$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$1.EmailValidator, selector: "[email][formControlName],[email][formControl],[email][ngModel]", inputs: ["email"] }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2$1.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }] });
|
|
1145
1152
|
}
|
|
1146
1153
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: Oslinput, decorators: [{
|
|
1147
1154
|
type: Component,
|
|
@@ -1194,6 +1201,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1194
1201
|
}], onlyChars: [{
|
|
1195
1202
|
type: Input,
|
|
1196
1203
|
args: ['onlyChars']
|
|
1204
|
+
}], isCapitalize: [{
|
|
1205
|
+
type: Input,
|
|
1206
|
+
args: ['isCapitalize']
|
|
1197
1207
|
}], decimalPortion: [{
|
|
1198
1208
|
type: Input,
|
|
1199
1209
|
args: ['decimalPortion']
|
|
@@ -2180,11 +2190,11 @@ class DynamicForm {
|
|
|
2180
2190
|
elem.change(this.model, undefined, selectedObj);
|
|
2181
2191
|
}
|
|
2182
2192
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DynamicForm, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
2183
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DynamicForm, isStandalone: false, selector: "osl-dynamic-form", inputs: { elements: "elements", model: "model", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme" }, outputs: { modelChange: "modelChange" }, usesOnChanges: true, ngImport: i0, template: "@if(elements && elements.length > 0) {\r\n <div class=\"row align-items-center w-100\">\r\n @for(elem of elements; track elem) {\r\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<ng-template #formField let-elem>\r\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\r\n @switch (elem.elementType) {\r\n\r\n @case (\"textbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-input\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [type]=\"elem.inputType || 'text'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [mask]=\"elem.mask || ''\"\r\n [min]=\"elem.min ?? ''\"\r\n [max]=\"elem.max ?? ''\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [prefixIcon]=\"elem.prefixIcon || ''\"\r\n [suffixIcon]=\"elem.suffixIcon || ''\"\r\n [onlyChars]=\"!!elem.onlyChars\"\r\n [decimalPortion]=\"elem.decimalPortion ?? null\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-input>\r\n </div>\r\n }\r\n\r\n @case (\"textarea\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-textarea\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [rows]=\"elem.textareaRows || 3\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [characterCounter]=\"!!elem.characterCounter\"\r\n [resize]=\"elem.resize || 'none'\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-textarea>\r\n </div>\r\n }\r\n\r\n @case (\"select\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-select\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n [clearable]=\"!!elem.clearable\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-select>\r\n </div>\r\n }\r\n\r\n @case (\"radio\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-radio\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [inline]=\"!!elem.inline\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-radio>\r\n </div>\r\n }\r\n\r\n @case (\"slide-toggle\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-slide-toggle\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [labelPosition]=\"elem.labelPosition || 'after'\"\r\n [trueLabel]=\"elem.trueLabel || ''\"\r\n [falseLabel]=\"elem.falseLabel || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-slide-toggle>\r\n </div>\r\n }\r\n\r\n @case (\"autocomplete\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-autocomplete\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [methodName]=\"elem.apiMethod\"\r\n [service]=\"elem.apiService\"\r\n [object]=\"model[elem.objectName]\"\r\n [searchType]=\"elem.searchType\"\r\n [configMethodName]=\"elem.apiConfigMethod\"\r\n [isLister]=\"elem.isListerAutocomplete\"\r\n \r\n \r\n ></osl-autocomplete>\r\n </div>\r\n }\r\n\r\n @case (\"file-uploader\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-file-upload\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [accept]=\"elem.accept || ''\"\r\n [multiple]=\"!!elem.multiple\"\r\n [maxSize]=\"elem.maxFileSize || 0\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-file-upload>\r\n </div>\r\n }\r\n\r\n @case (\"datepicker\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-datepicker\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [dateType]=\"elem.dateType || 'date'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [minDate]=\"elem.minDate || ''\"\r\n [maxDate]=\"elem.maxDate || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-datepicker>\r\n </div>\r\n }\r\n\r\n @case (\"checkbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-checkbox\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [indeterminate]=\"!!elem.indeterminate\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-checkbox>\r\n </div>\r\n }\r\n\r\n @case (\"button\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-button\r\n \r\n [label]=\"elem.label\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n variant=\"secondary\"\r\n (clickEv)=\"elem.change ? elem.change(model) : null\"\r\n ></osl-button>\r\n </div>\r\n }\r\n\r\n @case (\"fieldset\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <fieldset class=\"osl-fieldset\">\r\n @if(elem.label) {\r\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\r\n </legend>\r\n }\r\n <div class=\"row w-100 osl-fieldset-body\">\r\n @for(innerElem of elem.rows; track innerElem) {\r\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </fieldset>\r\n </div>\r\n }\r\n\r\n @case (\"templateRef\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\r\n\r\n \r\n </div>\r\n }\r\n @case (\"spacer\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <!-- <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container> -->\r\n\r\n \r\n </div>\r\n }\r\n\r\n }\r\n }\r\n</ng-template>\r\n", styles: [".osl-fieldset{border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:10px;padding:0 16px 16px;margin:0;background:linear-gradient(135deg,#f9fafb,#fff);box-shadow:0 1px 4px #0000000d;position:relative;transition:box-shadow .2s}.osl-fieldset:focus-within{box-shadow:0 0 0 3px #6366f114,0 2px 8px #0000000f;border-color:var(--osl-primary, #6366f1)}.osl-fieldset-legend{padding:0 6px;margin-left:8px;line-height:1;float:none;width:auto}.osl-fieldset-legend__text{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:#fff;padding:2px 10px;border:1.5px solid var(--osl-border-color, #e5e7eb);box-shadow:0 1px 3px #6366f11a}.osl-fieldset-body{margin-top:4px}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "skeletonLoading", "skeletonTheme", "onlyChars", "decimalPortion"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: Osltextarea, selector: "osl-textarea", inputs: ["label", "rows", "required", "disabled", "model", "placeholder", "maxLength", "minLength", "characterCounter", "resize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSelect, selector: "osl-select", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "clearable", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslRadio, selector: "osl-radio", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "inline", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSlideToggle, selector: "osl-slide-toggle", inputs: ["label", "disabled", "model", "labelPosition", "trueLabel", "falseLabel", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslAutocomplete, selector: "osl-autocomplete", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "searchType", "methodName", "configMethodName", "service", "object", "skeletonLoading", "skeletonTheme", "isLister"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslDatepicker, selector: "osl-datepicker", inputs: ["label", "required", "disabled", "model", "dateType", "placeholder", "minDate", "maxDate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }] });
|
|
2193
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DynamicForm, isStandalone: false, selector: "osl-dynamic-form", inputs: { elements: "elements", model: "model", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme" }, outputs: { modelChange: "modelChange" }, usesOnChanges: true, ngImport: i0, template: "@if(elements && elements.length > 0) {\r\n <div class=\"row align-items-center w-100\">\r\n @for(elem of elements; track elem) {\r\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<ng-template #formField let-elem>\r\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\r\n @switch (elem.elementType) {\r\n\r\n @case (\"textbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-input\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [type]=\"elem.inputType || 'text'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [mask]=\"elem.mask || ''\"\r\n [min]=\"elem.min ?? ''\"\r\n [max]=\"elem.max ?? ''\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [prefixIcon]=\"elem.prefixIcon || ''\"\r\n [suffixIcon]=\"elem.suffixIcon || ''\"\r\n [onlyChars]=\"!!elem.onlyChars\"\r\n [decimalPortion]=\"elem.decimalPortion ?? null\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [isCapitalize]=\"elem.isCapitalize\"\r\n ></osl-input>\r\n </div>\r\n }\r\n\r\n @case (\"textarea\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-textarea\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [rows]=\"elem.textareaRows || 3\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [characterCounter]=\"!!elem.characterCounter\"\r\n [resize]=\"elem.resize || 'none'\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-textarea>\r\n </div>\r\n }\r\n\r\n @case (\"select\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-select\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n [clearable]=\"!!elem.clearable\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-select>\r\n </div>\r\n }\r\n\r\n @case (\"radio\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-radio\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [inline]=\"!!elem.inline\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-radio>\r\n </div>\r\n }\r\n\r\n @case (\"slide-toggle\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-slide-toggle\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [labelPosition]=\"elem.labelPosition || 'after'\"\r\n [trueLabel]=\"elem.trueLabel || ''\"\r\n [falseLabel]=\"elem.falseLabel || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-slide-toggle>\r\n </div>\r\n }\r\n\r\n @case (\"autocomplete\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-autocomplete\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [methodName]=\"elem.apiMethod\"\r\n [service]=\"elem.apiService\"\r\n [object]=\"model[elem.objectName]\"\r\n [searchType]=\"elem.searchType\"\r\n [configMethodName]=\"elem.apiConfigMethod\"\r\n [isLister]=\"elem.isListerAutocomplete\"\r\n \r\n \r\n ></osl-autocomplete>\r\n </div>\r\n }\r\n\r\n @case (\"file-uploader\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-file-upload\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [accept]=\"elem.accept || ''\"\r\n [multiple]=\"!!elem.multiple\"\r\n [maxSize]=\"elem.maxFileSize || 0\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-file-upload>\r\n </div>\r\n }\r\n\r\n @case (\"datepicker\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-datepicker\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [dateType]=\"elem.dateType || 'date'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [minDate]=\"elem.minDate || ''\"\r\n [maxDate]=\"elem.maxDate || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-datepicker>\r\n </div>\r\n }\r\n\r\n @case (\"checkbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-checkbox\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [indeterminate]=\"!!elem.indeterminate\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-checkbox>\r\n </div>\r\n }\r\n\r\n @case (\"button\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-button\r\n \r\n [label]=\"elem.label\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n variant=\"secondary\"\r\n (clickEv)=\"elem.change ? elem.change(model) : null\"\r\n ></osl-button>\r\n </div>\r\n }\r\n\r\n @case (\"fieldset\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <fieldset class=\"osl-fieldset\">\r\n @if(elem.label) {\r\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\r\n </legend>\r\n }\r\n <div class=\"row w-100 osl-fieldset-body\">\r\n @for(innerElem of elem.rows; track innerElem) {\r\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </fieldset>\r\n </div>\r\n }\r\n\r\n @case (\"templateRef\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\r\n\r\n \r\n </div>\r\n }\r\n @case (\"spacer\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <!-- <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container> -->\r\n\r\n \r\n </div>\r\n }\r\n\r\n }\r\n }\r\n</ng-template>\r\n", styles: [".osl-fieldset{border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:10px;padding:0 16px 16px;margin:0;background:linear-gradient(135deg,#f9fafb,#fff);box-shadow:0 1px 4px #0000000d;position:relative;transition:box-shadow .2s}.osl-fieldset:focus-within{box-shadow:0 0 0 3px #6366f114,0 2px 8px #0000000f;border-color:var(--osl-primary, #6366f1)}.osl-fieldset-legend{padding:0 6px;margin-left:8px;line-height:1;float:none;width:auto}.osl-fieldset-legend__text{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:#fff;padding:2px 10px;border:1.5px solid var(--osl-border-color, #e5e7eb);box-shadow:0 1px 3px #6366f11a}.osl-fieldset-body{margin-top:4px}\n"], dependencies: [{ kind: "directive", type: i1$2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: OslSkeletonDirective, selector: "[oslSkeleton]", inputs: ["oslSkeleton", "oslSkeletonType", "oslSkeletonAnimation", "oslSkeletonTheme", "oslSkeletonColor", "oslSkeletonHighlight", "oslSkeletonRadius", "oslSkeletonRows", "oslSkeletonRowGap", "oslSkeletonZIndex", "oslSkeletonDelay", "oslSkeletonDuration", "oslSkeletonMinHeight", "oslSkeletonForceReread", "oslSkeletonCircleSize", "oslSkeletonListItems", "oslSkeletonTableRows", "oslSkeletonTableCols", "oslSkeletonCardLines", "oslSkeletonBgColor"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "skeletonLoading", "skeletonTheme", "onlyChars", "isCapitalize", "decimalPortion"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: Osltextarea, selector: "osl-textarea", inputs: ["label", "rows", "required", "disabled", "model", "placeholder", "maxLength", "minLength", "characterCounter", "resize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSelect, selector: "osl-select", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "clearable", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslRadio, selector: "osl-radio", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "inline", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSlideToggle, selector: "osl-slide-toggle", inputs: ["label", "disabled", "model", "labelPosition", "trueLabel", "falseLabel", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslAutocomplete, selector: "osl-autocomplete", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "searchType", "methodName", "configMethodName", "service", "object", "skeletonLoading", "skeletonTheme", "isLister"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslDatepicker, selector: "osl-datepicker", inputs: ["label", "required", "disabled", "model", "dateType", "placeholder", "minDate", "maxDate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslButton, selector: "osl-button", inputs: ["label", "icon", "variant", "size", "disabled", "loading", "type", "fullWidth"], outputs: ["clickEv"] }] });
|
|
2184
2194
|
}
|
|
2185
2195
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DynamicForm, decorators: [{
|
|
2186
2196
|
type: Component,
|
|
2187
|
-
args: [{ selector: 'osl-dynamic-form', standalone: false, template: "@if(elements && elements.length > 0) {\r\n <div class=\"row align-items-center w-100\">\r\n @for(elem of elements; track elem) {\r\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<ng-template #formField let-elem>\r\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\r\n @switch (elem.elementType) {\r\n\r\n @case (\"textbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-input\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [type]=\"elem.inputType || 'text'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [mask]=\"elem.mask || ''\"\r\n [min]=\"elem.min ?? ''\"\r\n [max]=\"elem.max ?? ''\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [prefixIcon]=\"elem.prefixIcon || ''\"\r\n [suffixIcon]=\"elem.suffixIcon || ''\"\r\n [onlyChars]=\"!!elem.onlyChars\"\r\n [decimalPortion]=\"elem.decimalPortion ?? null\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-input>\r\n </div>\r\n }\r\n\r\n @case (\"textarea\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-textarea\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [rows]=\"elem.textareaRows || 3\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [characterCounter]=\"!!elem.characterCounter\"\r\n [resize]=\"elem.resize || 'none'\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-textarea>\r\n </div>\r\n }\r\n\r\n @case (\"select\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-select\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n [clearable]=\"!!elem.clearable\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-select>\r\n </div>\r\n }\r\n\r\n @case (\"radio\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-radio\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [inline]=\"!!elem.inline\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-radio>\r\n </div>\r\n }\r\n\r\n @case (\"slide-toggle\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-slide-toggle\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [labelPosition]=\"elem.labelPosition || 'after'\"\r\n [trueLabel]=\"elem.trueLabel || ''\"\r\n [falseLabel]=\"elem.falseLabel || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-slide-toggle>\r\n </div>\r\n }\r\n\r\n @case (\"autocomplete\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-autocomplete\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [methodName]=\"elem.apiMethod\"\r\n [service]=\"elem.apiService\"\r\n [object]=\"model[elem.objectName]\"\r\n [searchType]=\"elem.searchType\"\r\n [configMethodName]=\"elem.apiConfigMethod\"\r\n [isLister]=\"elem.isListerAutocomplete\"\r\n \r\n \r\n ></osl-autocomplete>\r\n </div>\r\n }\r\n\r\n @case (\"file-uploader\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-file-upload\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [accept]=\"elem.accept || ''\"\r\n [multiple]=\"!!elem.multiple\"\r\n [maxSize]=\"elem.maxFileSize || 0\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-file-upload>\r\n </div>\r\n }\r\n\r\n @case (\"datepicker\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-datepicker\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [dateType]=\"elem.dateType || 'date'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [minDate]=\"elem.minDate || ''\"\r\n [maxDate]=\"elem.maxDate || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-datepicker>\r\n </div>\r\n }\r\n\r\n @case (\"checkbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-checkbox\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [indeterminate]=\"!!elem.indeterminate\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-checkbox>\r\n </div>\r\n }\r\n\r\n @case (\"button\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-button\r\n \r\n [label]=\"elem.label\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n variant=\"secondary\"\r\n (clickEv)=\"elem.change ? elem.change(model) : null\"\r\n ></osl-button>\r\n </div>\r\n }\r\n\r\n @case (\"fieldset\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <fieldset class=\"osl-fieldset\">\r\n @if(elem.label) {\r\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\r\n </legend>\r\n }\r\n <div class=\"row w-100 osl-fieldset-body\">\r\n @for(innerElem of elem.rows; track innerElem) {\r\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </fieldset>\r\n </div>\r\n }\r\n\r\n @case (\"templateRef\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\r\n\r\n \r\n </div>\r\n }\r\n @case (\"spacer\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <!-- <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container> -->\r\n\r\n \r\n </div>\r\n }\r\n\r\n }\r\n }\r\n</ng-template>\r\n", styles: [".osl-fieldset{border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:10px;padding:0 16px 16px;margin:0;background:linear-gradient(135deg,#f9fafb,#fff);box-shadow:0 1px 4px #0000000d;position:relative;transition:box-shadow .2s}.osl-fieldset:focus-within{box-shadow:0 0 0 3px #6366f114,0 2px 8px #0000000f;border-color:var(--osl-primary, #6366f1)}.osl-fieldset-legend{padding:0 6px;margin-left:8px;line-height:1;float:none;width:auto}.osl-fieldset-legend__text{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:#fff;padding:2px 10px;border:1.5px solid var(--osl-border-color, #e5e7eb);box-shadow:0 1px 3px #6366f11a}.osl-fieldset-body{margin-top:4px}\n"] }]
|
|
2197
|
+
args: [{ selector: 'osl-dynamic-form', standalone: false, template: "@if(elements && elements.length > 0) {\r\n <div class=\"row align-items-center w-100\">\r\n @for(elem of elements; track elem) {\r\n @if(!(elem.hideIf ? elem.hideIf(model) : elem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: elem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n}\r\n\r\n<ng-template #formField let-elem>\r\n @if(elem.hideIf ? !elem.hideIf(model) : true) {\r\n @switch (elem.elementType) {\r\n\r\n @case (\"textbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-input\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [type]=\"elem.inputType || 'text'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [mask]=\"elem.mask || ''\"\r\n [min]=\"elem.min ?? ''\"\r\n [max]=\"elem.max ?? ''\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [prefixIcon]=\"elem.prefixIcon || ''\"\r\n [suffixIcon]=\"elem.suffixIcon || ''\"\r\n [onlyChars]=\"!!elem.onlyChars\"\r\n [decimalPortion]=\"elem.decimalPortion ?? null\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [isCapitalize]=\"elem.isCapitalize\"\r\n ></osl-input>\r\n </div>\r\n }\r\n\r\n @case (\"textarea\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-textarea\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [rows]=\"elem.textareaRows || 3\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [maxLength]=\"elem.maxLength ?? null\"\r\n [minLength]=\"elem.minLength ?? null\"\r\n [characterCounter]=\"!!elem.characterCounter\"\r\n [resize]=\"elem.resize || 'none'\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-textarea>\r\n </div>\r\n }\r\n\r\n @case (\"select\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-select\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.selectPlaceholder || 'Select...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n [clearable]=\"!!elem.clearable\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-select>\r\n </div>\r\n }\r\n\r\n @case (\"radio\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-radio\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [inline]=\"!!elem.inline\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-radio>\r\n </div>\r\n }\r\n\r\n @case (\"slide-toggle\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-slide-toggle\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [labelPosition]=\"elem.labelPosition || 'after'\"\r\n [trueLabel]=\"elem.trueLabel || ''\"\r\n [falseLabel]=\"elem.falseLabel || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-slide-toggle>\r\n </div>\r\n }\r\n\r\n @case (\"autocomplete\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-autocomplete\r\n [label]=\"elem.label\"\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [datasource]=\"elem.datasource || []\"\r\n [displayField]=\"elem.displayField || ''\"\r\n [valueField]=\"elem.valueField || ''\"\r\n [placeholder]=\"elem.autocompletePlaceholder || 'Type to search...'\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n (changeEv)=\"elem.change ? onSelectChange(elem, $event) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n [methodName]=\"elem.apiMethod\"\r\n [service]=\"elem.apiService\"\r\n [object]=\"model[elem.objectName]\"\r\n [searchType]=\"elem.searchType\"\r\n [configMethodName]=\"elem.apiConfigMethod\"\r\n [isLister]=\"elem.isListerAutocomplete\"\r\n \r\n \r\n ></osl-autocomplete>\r\n </div>\r\n }\r\n\r\n @case (\"file-uploader\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-file-upload\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [accept]=\"elem.accept || ''\"\r\n [multiple]=\"!!elem.multiple\"\r\n [maxSize]=\"elem.maxFileSize || 0\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-file-upload>\r\n </div>\r\n }\r\n\r\n @case (\"datepicker\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-datepicker\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [dateType]=\"elem.dateType || 'date'\"\r\n [placeholder]=\"elem.placeholder || ''\"\r\n [minDate]=\"elem.minDate || ''\"\r\n [maxDate]=\"elem.maxDate || ''\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-datepicker>\r\n </div>\r\n }\r\n\r\n @case (\"checkbox\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-checkbox\r\n [skeletonLoading]=\"skeletonLoading\"\r\n [skeletonTheme]=\"skeletonTheme\"\r\n\r\n [label]=\"elem.label\"\r\n [disabled]=\"elem.disabledIf ? elem.disabledIf(model) : !!elem.disabled\"\r\n [required]=\"elem.requiredIf ? elem.requiredIf(model) : !!elem.required\"\r\n [indeterminate]=\"!!elem.indeterminate\"\r\n (changeEv)=\"elem.change ? elem.change(model) : null\"\r\n [(model)]=\"model[elem.key]\"\r\n ></osl-checkbox>\r\n </div>\r\n }\r\n\r\n @case (\"button\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-2'\">\r\n <osl-button\r\n \r\n [label]=\"elem.label\"\r\n [loading]=\"elem.loadingIf ? elem.loadingIf(model) : false\"\r\n variant=\"secondary\"\r\n (clickEv)=\"elem.change ? elem.change(model) : null\"\r\n ></osl-button>\r\n </div>\r\n }\r\n\r\n @case (\"fieldset\") {\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <fieldset class=\"osl-fieldset\">\r\n @if(elem.label) {\r\n <legend class=\"osl-fieldset-legend\" [oslSkeleton]=\"skeletonLoading\" [oslSkeletonTheme]=\"skeletonTheme\">\r\n <span class=\"osl-fieldset-legend__text\">{{ elem.label }}</span>\r\n </legend>\r\n }\r\n <div class=\"row w-100 osl-fieldset-body\">\r\n @for(innerElem of elem.rows; track innerElem) {\r\n @if(!(innerElem.hideIf ? innerElem.hideIf(model) : innerElem.hide)) {\r\n <ng-container *ngTemplateOutlet=\"formField; context: { $implicit: innerElem }\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </fieldset>\r\n </div>\r\n }\r\n\r\n @case (\"templateRef\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container>\r\n\r\n \r\n </div>\r\n }\r\n @case (\"spacer\"){\r\n <div [class]=\"'col-md-'+elem.columns+' mt-3'\">\r\n <!-- <ng-container *ngTemplateOutlet=\"elem.templateRef; context: { $implicit: elem}\"></ng-container> -->\r\n\r\n \r\n </div>\r\n }\r\n\r\n }\r\n }\r\n</ng-template>\r\n", styles: [".osl-fieldset{border:1.5px solid var(--osl-border-color, #e5e7eb);border-radius:10px;padding:0 16px 16px;margin:0;background:linear-gradient(135deg,#f9fafb,#fff);box-shadow:0 1px 4px #0000000d;position:relative;transition:box-shadow .2s}.osl-fieldset:focus-within{box-shadow:0 0 0 3px #6366f114,0 2px 8px #0000000f;border-color:var(--osl-primary, #6366f1)}.osl-fieldset-legend{padding:0 6px;margin-left:8px;line-height:1;float:none;width:auto}.osl-fieldset-legend__text{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;background:#fff;padding:2px 10px;border:1.5px solid var(--osl-border-color, #e5e7eb);box-shadow:0 1px 3px #6366f11a}.osl-fieldset-body{margin-top:4px}\n"] }]
|
|
2188
2198
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { elements: [{
|
|
2189
2199
|
type: Input,
|
|
2190
2200
|
args: ['elements']
|
|
@@ -2556,13 +2566,16 @@ class OslSetup {
|
|
|
2556
2566
|
this._dialogRef = null;
|
|
2557
2567
|
}
|
|
2558
2568
|
async saveDialog() {
|
|
2569
|
+
let isSuccess = false;
|
|
2559
2570
|
if (this.onSave) {
|
|
2560
2571
|
this.saveLoading = true;
|
|
2561
|
-
await this.onSave({ model: { ...this.dialogModel }, mode: this.dialogMode });
|
|
2572
|
+
isSuccess = await this.onSave({ model: { ...this.dialogModel }, mode: this.dialogMode });
|
|
2562
2573
|
this.saveLoading = false;
|
|
2563
2574
|
}
|
|
2564
|
-
|
|
2565
|
-
|
|
2575
|
+
if (isSuccess) {
|
|
2576
|
+
this._dialogRef?.close();
|
|
2577
|
+
this._dialogRef = null;
|
|
2578
|
+
}
|
|
2566
2579
|
}
|
|
2567
2580
|
_openDialog() {
|
|
2568
2581
|
this._dialogRef = this._injector.get(MatDialog).open(DialogWrapper, {
|
|
@@ -2806,7 +2819,7 @@ class OslFormGrid {
|
|
|
2806
2819
|
elem.change(row, i, selectedObj);
|
|
2807
2820
|
}
|
|
2808
2821
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslFormGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2809
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslFormGrid, isStandalone: false, selector: "osl-form-grid", inputs: { columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", canAdd: "canAdd", canDelete: "canDelete", loading: "loading", tableHeight: "tableHeight", footerColumns: "footerColumns" }, outputs: { datasourceChange: "datasourceChange", rowAdd: "rowAdd", rowDelete: "rowDelete" }, ngImport: i0, template: "<div class=\"osl-fg-wrapper\">\r\n\r\n <div class=\"osl-fg-table-container\" [style.height]=\"tableHeight\">\r\n <table class=\"osl-fg-table\">\r\n\r\n <thead class=\"osl-fg-thead\">\r\n <tr>\r\n <!-- Actions column: + add button in header -->\r\n @if (hasActions) {\r\n <th class=\"osl-fg-th osl-fg-th--actions\">\r\n @if (canAdd) {\r\n <button class=\"osl-grid-icon-btn\" (click)=\"addRow()\" [disabled]=\"loading\" title=\"Add row\">\r\n <mat-icon>add</mat-icon>\r\n </button>\r\n }\r\n </th>\r\n }\r\n @for (col of columns; track col.key) {\r\n <th class=\"osl-fg-th\" [style.width]=\"col.width\">\r\n {{ col.displayName }}\r\n @if (colRequired(col)) {\r\n <span class=\"osl-fg-th-required\">*</span>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n\r\n @if (loading) {\r\n @for (sk of skeletonRows; track $index) {\r\n <tr class=\"osl-fg-row osl-fg-row--skeleton\">\r\n @if (hasActions) {\r\n <td class=\"osl-fg-td osl-fg-td--actions\">\r\n <div class=\"osl-fg-skeleton osl-fg-skeleton--sm\"></div>\r\n </td>\r\n }\r\n @for (col of columns; track col.key) {\r\n <td class=\"osl-fg-td\"><div class=\"osl-fg-skeleton\"></div></td>\r\n }\r\n </tr>\r\n }\r\n\r\n } @else if (pagedData.length === 0) {\r\n <tr>\r\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0)\" class=\"osl-fg-empty\">\r\n <div class=\"osl-fg-empty-inner\">\r\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\r\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\r\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\r\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\r\n </svg>\r\n <p>No rows yet.{{ canAdd ? ' Use + to get started.' : '' }}</p>\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n } @else {\r\n @for (row of pagedData; track $index; let i = $index) {\r\n <tr class=\"osl-fg-row\">\r\n\r\n <!-- Delete button first -->\r\n @if (hasActions) {\r\n <td class=\"osl-fg-td osl-fg-td--actions\">\r\n @if (canDelete) {\r\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\" (click)=\"deleteRow(i)\" title=\"Delete\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n }\r\n </td>\r\n }\r\n\r\n @for (col of columns; track col.key) {\r\n <td class=\"osl-fg-td\" [style.width]=\"col.width\">\r\n @if (col.formElem) {\r\n <div class=\"osl-fg-cell-form\">\r\n @switch (col.formElem.elementType) {\r\n\r\n @case ('textbox') {\r\n <osl-input\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [type]=\"col.formElem.inputType || 'text'\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [mask]=\"col.formElem.mask || ''\"\r\n [min]=\"col.formElem.min ?? ''\"\r\n [max]=\"col.formElem.max ?? ''\"\r\n [minLength]=\"col.formElem.minLength ?? null\"\r\n [maxLength]=\"col.formElem.maxLength ?? null\"\r\n [prefixIcon]=\"col.formElem.prefixIcon || ''\"\r\n [suffixIcon]=\"col.formElem.suffixIcon || ''\"\r\n [decimalPortion]=\"col.formElem.decimalPortion ?? null\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-input>\r\n }\r\n\r\n @case ('textarea') {\r\n <osl-textarea\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [rows]=\"col.formElem.textareaRows || 3\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [maxLength]=\"col.formElem.maxLength ?? null\"\r\n [minLength]=\"col.formElem.minLength ?? null\"\r\n [characterCounter]=\"!!col.formElem.characterCounter\"\r\n [resize]=\"col.formElem.resize || 'none'\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-textarea>\r\n }\r\n\r\n @case ('select') {\r\n <osl-select\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [placeholder]=\"col.formElem.selectPlaceholder || col.displayName\"\r\n [loading]=\"isLoading(row, col.formElem)\"\r\n [clearable]=\"!!col.formElem.clearable\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\r\n </osl-select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <osl-autocomplete\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [placeholder]=\"col.formElem.autocompletePlaceholder || col.displayName\"\r\n [loading]=\"isLoading(row, col.formElem)\"\r\n [searchType]=\"col.formElem.searchType ? col.formElem.searchType : 'Local'\"\r\n [methodName]=\"col.formElem.apiMethod || ''\"\r\n [configMethodName]=\"col.formElem.apiConfigMethod || ''\"\r\n [service]=\"col.formElem.apiService\"\r\n [object]=\"col.formElem.objectName ? row[col.formElem.objectName] : null\"\r\n [isLister]=\"!!col.formElem.isListerAutocomplete\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\r\n </osl-autocomplete>\r\n }\r\n\r\n @case ('radio') {\r\n <osl-radio\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [inline]=\"!!col.formElem.inline\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-radio>\r\n }\r\n\r\n @case ('checkbox') {\r\n <osl-checkbox\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [indeterminate]=\"!!col.formElem.indeterminate\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-checkbox>\r\n }\r\n\r\n @case ('slide-toggle') {\r\n <osl-slide-toggle\r\n [label]=\"''\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [labelPosition]=\"col.formElem.labelPosition || 'after'\"\r\n [trueLabel]=\"col.formElem.trueLabel || ''\"\r\n [falseLabel]=\"col.formElem.falseLabel || ''\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-slide-toggle>\r\n }\r\n\r\n @case ('datepicker') {\r\n <osl-datepicker\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [dateType]=\"col.formElem.dateType || 'date'\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [minDate]=\"col.formElem.minDate || ''\"\r\n [maxDate]=\"col.formElem.maxDate || ''\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-datepicker>\r\n }\r\n\r\n @case ('file-uploader') {\r\n <osl-file-upload\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [accept]=\"col.formElem.accept || ''\"\r\n [multiple]=\"!!col.formElem.multiple\"\r\n [maxSize]=\"col.formElem.maxFileSize || 0\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-file-upload>\r\n }\r\n\r\n }\r\n </div>\r\n } @else {\r\n <span class=\"osl-fg-cell-text\">{{ row[col.key] ?? '--' }}</span>\r\n }\r\n </td>\r\n }\r\n\r\n </tr>\r\n }\r\n }\r\n\r\n </tbody>\r\n\r\n @if (footerColumns && footerColumns.length > 0 && datasource && datasource.length > 0) {\r\n <tfoot class=\"osl-fg-tfoot\">\r\n <tr class=\"osl-fg-footer-row\">\r\n @for (fcol of footerColumns; track $index) {\r\n <td\r\n [class]=\"'osl-fg-footer-td ' + (fcol.class || '')\"\r\n [attr.colspan]=\"fcol.colspan || 1\">\r\n {{ fcol.displayFn ? fcol.displayFn(datasource) : (fcol.display || '') }}\r\n </td>\r\n }\r\n </tr>\r\n </tfoot>\r\n }\r\n\r\n </table>\r\n </div>\r\n\r\n @if (isPaginated) {\r\n <div class=\"osl-fg-pagination\">\r\n\r\n <span class=\"osl-fg-pagination__info\">\r\n @if (loading) {\r\n Loading...\r\n } @else if (_total > 0) {\r\n {{ startRecord }}\u2013{{ endRecord }} of {{ _total }} rows\r\n } @else {\r\n No rows\r\n }\r\n </span>\r\n\r\n <div class=\"osl-fg-pagination__controls\">\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage === 1 || loading\">\u00AB</button>\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage === 1 || loading\">\u2039 Prev</button>\r\n\r\n @for (page of pageNumbers; track $index) {\r\n @if (page === -1) {\r\n <span class=\"osl-fg-page-ellipsis\">\u2026</span>\r\n } @else {\r\n <button\r\n class=\"osl-fg-page-btn osl-fg-page-btn--num\"\r\n [class.osl-fg-page-btn--active]=\"page === currentPage\"\r\n [disabled]=\"loading\"\r\n (click)=\"goToPage(page)\">\r\n {{ page }}\r\n </button>\r\n }\r\n }\r\n\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage === totalPages || loading\">Next \u203A</button>\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage === totalPages || loading\">\u00BB</button>\r\n </div>\r\n\r\n <div class=\"osl-fg-pagination__size\">\r\n <select class=\"osl-fg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\r\n @for (opt of pageSizeOptions; track opt) {\r\n <option [value]=\"opt\">{{ opt }} per page</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n </div>\r\n }\r\n\r\n</div>\r\n", styles: [".osl-fg-wrapper{display:flex;flex-direction:column;width:100%;font-size:var(--osl-text-font-size);font-family:inherit;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);overflow:hidden;background:#fff}.osl-fg-table-container{overflow-x:auto;overflow-y:auto;min-height:120px}.osl-fg-table{width:100%;border-collapse:collapse;table-layout:auto}.osl-fg-thead{position:sticky;top:0;z-index:2}.osl-fg-th{background:#f3f4f6;color:#4b5563;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;padding:10px 12px;text-align:left;border-bottom:2px solid var(--osl-border-color, #e5e7eb);border-right:1px solid var(--osl-border-color, #e5e7eb);white-space:nowrap;vertical-align:middle}.osl-fg-th:last-child{border-right:none}.osl-fg-th--actions{width:44px;min-width:44px;text-align:center;padding:6px}.osl-fg-th-required{color:#ef4444;margin-left:2px;font-size:14px;line-height:1;vertical-align:middle}.osl-fg-row{border-bottom:1px solid #f0f0f0;transition:background .1s}.osl-fg-row:nth-child(odd){background:#f8fafc}.osl-fg-row:nth-child(2n){background:#fff}.osl-fg-row:hover{background:#eef2ff!important}.osl-fg-row:last-child{border-bottom:none}.osl-fg-row--skeleton{pointer-events:none}.osl-fg-td{padding:4px 10px;vertical-align:middle;border-right:1px solid #f0f0f0}.osl-fg-td:last-child{border-right:none}.osl-fg-td--actions{width:44px;min-width:44px;text-align:center;padding:4px 6px}.osl-fg-cell-form{min-width:100px}.osl-fg-cell-form ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none!important}.osl-fg-cell-form ::ng-deep .mat-mdc-form-field,.osl-fg-cell-form ::ng-deep mat-form-field{width:100%}.osl-fg-cell-form ::ng-deep .mat-mdc-text-field-wrapper{padding-top:0}.osl-fg-cell-text{color:#111827;font-size:var(--osl-text-font-size)}.osl-fg-add-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;border-radius:6px;background:var(--osl-primary, #6366f1);color:#fff;cursor:pointer;padding:0;box-shadow:0 1px 4px #6366f159;transition:background .15s,box-shadow .15s,transform .1s}.osl-fg-add-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.osl-fg-add-btn:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);box-shadow:0 2px 8px #6366f173;transform:scale(1.05)}.osl-fg-add-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}.osl-fg-delete-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1.5px solid transparent;border-radius:6px;background:transparent;color:#d1d5db;cursor:pointer;padding:0;transition:border-color .15s,color .15s,background .15s}.osl-fg-delete-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.osl-fg-delete-btn:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}.osl-fg-empty{padding:40px 16px;text-align:center}.osl-fg-empty-inner{display:flex;flex-direction:column;align-items:center;gap:10px;color:#9ca3af}.osl-fg-empty-inner svg{opacity:.4}.osl-fg-empty-inner p{margin:0;font-size:13px}@keyframes osl-fg-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-fg-skeleton{height:36px;border-radius:6px;background:#e5e7eb;animation:osl-fg-pulse 1.4s ease-in-out infinite}.osl-fg-skeleton--sm{height:28px;width:28px;border-radius:4px;margin:0 auto}.osl-fg-tfoot{position:sticky;bottom:0;z-index:2}.osl-fg-footer-td{background:#f3f4f6;color:#4b5563;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;padding:10px 12px;text-align:left;border-top:2px solid var(--osl-border-color, #e5e7eb);border-right:1px solid var(--osl-border-color, #e5e7eb);white-space:nowrap;vertical-align:middle}.osl-fg-footer-td:last-child{border-right:none}.osl-fg-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-top:1px solid var(--osl-border-color, #e5e7eb);background:#f9fafb;flex-shrink:0;flex-wrap:wrap}.osl-fg-pagination__info{font-size:12px;color:#6b7280;white-space:nowrap;min-width:120px}.osl-fg-pagination__controls{display:flex;align-items:center;gap:3px;flex-wrap:nowrap}.osl-fg-pagination__size{display:flex;align-items:center;justify-content:flex-end;min-width:120px}.osl-fg-page-btn{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 10px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:13px;font-family:inherit;cursor:pointer;transition:background .12s,border-color .12s,color .12s;white-space:nowrap}.osl-fg-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af}.osl-fg-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-fg-page-btn--nav{font-size:12px;color:#6b7280}.osl-fg-page-btn--num{min-width:30px;padding:0;font-size:13px}.osl-fg-page-btn--active{background:var(--osl-primary, #6366f1);border-color:var(--osl-primary, #6366f1);color:#fff;font-weight:600}.osl-fg-page-btn--active:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);border-color:var(--osl-primary-hover, #4f46e5)}.osl-fg-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-fg-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:12px;font-family:inherit;cursor:pointer;outline:none}.osl-fg-page-size:focus{border-color:var(--osl-primary, #6366f1)}.osl-fg-page-size:disabled{opacity:.4;cursor:not-allowed}.osl-grid-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1.5px solid var(--osl-border-color);border-radius:var(--osl-border-radius);background:transparent;color:#6b7280;cursor:pointer;padding:0;transition:border-color .15s,color .15s,background .15s;margin:0 2px}.osl-grid-icon-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.osl-grid-icon-btn:hover{border-color:var(--osl-primary);color:var(--osl-primary);background:#6366f10f}.osl-grid-icon-btn--danger:hover{border-color:#ef4444;color:#ef4444;background:#ef44440f}\n"], dependencies: [{ kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "skeletonLoading", "skeletonTheme", "onlyChars", "decimalPortion"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: Osltextarea, selector: "osl-textarea", inputs: ["label", "rows", "required", "disabled", "model", "placeholder", "maxLength", "minLength", "characterCounter", "resize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSelect, selector: "osl-select", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "clearable", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslRadio, selector: "osl-radio", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "inline", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSlideToggle, selector: "osl-slide-toggle", inputs: ["label", "disabled", "model", "labelPosition", "trueLabel", "falseLabel", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslAutocomplete, selector: "osl-autocomplete", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "searchType", "methodName", "configMethodName", "service", "object", "skeletonLoading", "skeletonTheme", "isLister"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslDatepicker, selector: "osl-datepicker", inputs: ["label", "required", "disabled", "model", "dateType", "placeholder", "minDate", "maxDate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }] });
|
|
2822
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslFormGrid, isStandalone: false, selector: "osl-form-grid", inputs: { columns: "columns", datasource: "datasource", isPaginated: "isPaginated", pageSize: "pageSize", canAdd: "canAdd", canDelete: "canDelete", loading: "loading", tableHeight: "tableHeight", footerColumns: "footerColumns" }, outputs: { datasourceChange: "datasourceChange", rowAdd: "rowAdd", rowDelete: "rowDelete" }, ngImport: i0, template: "<div class=\"osl-fg-wrapper\">\r\n\r\n <div class=\"osl-fg-table-container\" [style.height]=\"tableHeight\">\r\n <table class=\"osl-fg-table\">\r\n\r\n <thead class=\"osl-fg-thead\">\r\n <tr>\r\n <!-- Actions column: + add button in header -->\r\n @if (hasActions) {\r\n <th class=\"osl-fg-th osl-fg-th--actions\">\r\n @if (canAdd) {\r\n <button class=\"osl-grid-icon-btn\" (click)=\"addRow()\" [disabled]=\"loading\" title=\"Add row\">\r\n <mat-icon>add</mat-icon>\r\n </button>\r\n }\r\n </th>\r\n }\r\n @for (col of columns; track col.key) {\r\n <th class=\"osl-fg-th\" [style.width]=\"col.width\">\r\n {{ col.displayName }}\r\n @if (colRequired(col)) {\r\n <span class=\"osl-fg-th-required\">*</span>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <tbody>\r\n\r\n @if (loading) {\r\n @for (sk of skeletonRows; track $index) {\r\n <tr class=\"osl-fg-row osl-fg-row--skeleton\">\r\n @if (hasActions) {\r\n <td class=\"osl-fg-td osl-fg-td--actions\">\r\n <div class=\"osl-fg-skeleton osl-fg-skeleton--sm\"></div>\r\n </td>\r\n }\r\n @for (col of columns; track col.key) {\r\n <td class=\"osl-fg-td\"><div class=\"osl-fg-skeleton\"></div></td>\r\n }\r\n </tr>\r\n }\r\n\r\n } @else if (pagedData.length === 0) {\r\n <tr>\r\n <td [attr.colspan]=\"columns.length + (hasActions ? 1 : 0)\" class=\"osl-fg-empty\">\r\n <div class=\"osl-fg-empty-inner\">\r\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\r\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/>\r\n <line x1=\"3\" y1=\"9\" x2=\"21\" y2=\"9\"/>\r\n <line x1=\"9\" y1=\"9\" x2=\"9\" y2=\"21\"/>\r\n </svg>\r\n <p>No rows yet.{{ canAdd ? ' Use + to get started.' : '' }}</p>\r\n </div>\r\n </td>\r\n </tr>\r\n\r\n } @else {\r\n @for (row of pagedData; track $index; let i = $index) {\r\n <tr class=\"osl-fg-row\">\r\n\r\n <!-- Delete button first -->\r\n @if (hasActions) {\r\n <td class=\"osl-fg-td osl-fg-td--actions\">\r\n @if (canDelete) {\r\n <button class=\"osl-grid-icon-btn osl-grid-icon-btn--danger\" (click)=\"deleteRow(i)\" title=\"Delete\">\r\n <mat-icon>delete_outline</mat-icon>\r\n </button>\r\n }\r\n </td>\r\n }\r\n\r\n @for (col of columns; track col.key) {\r\n <td class=\"osl-fg-td\" [style.width]=\"col.width\">\r\n @if (col.formElem) {\r\n <div class=\"osl-fg-cell-form\">\r\n @switch (col.formElem.elementType) {\r\n\r\n @case ('textbox') {\r\n <osl-input\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [type]=\"col.formElem.inputType || 'text'\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [mask]=\"col.formElem.mask || ''\"\r\n [min]=\"col.formElem.min ?? ''\"\r\n [max]=\"col.formElem.max ?? ''\"\r\n [minLength]=\"col.formElem.minLength ?? null\"\r\n [maxLength]=\"col.formElem.maxLength ?? null\"\r\n [prefixIcon]=\"col.formElem.prefixIcon || ''\"\r\n [suffixIcon]=\"col.formElem.suffixIcon || ''\"\r\n [decimalPortion]=\"col.formElem.decimalPortion ?? null\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-input>\r\n }\r\n\r\n @case ('textarea') {\r\n <osl-textarea\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [rows]=\"col.formElem.textareaRows || 3\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [maxLength]=\"col.formElem.maxLength ?? null\"\r\n [minLength]=\"col.formElem.minLength ?? null\"\r\n [characterCounter]=\"!!col.formElem.characterCounter\"\r\n [resize]=\"col.formElem.resize || 'none'\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-textarea>\r\n }\r\n\r\n @case ('select') {\r\n <osl-select\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [placeholder]=\"col.formElem.selectPlaceholder || col.displayName\"\r\n [loading]=\"isLoading(row, col.formElem)\"\r\n [clearable]=\"!!col.formElem.clearable\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\r\n </osl-select>\r\n }\r\n\r\n @case ('autocomplete') {\r\n <osl-autocomplete\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [placeholder]=\"col.formElem.autocompletePlaceholder || col.displayName\"\r\n [loading]=\"isLoading(row, col.formElem)\"\r\n [searchType]=\"col.formElem.searchType ? col.formElem.searchType : 'Local'\"\r\n [methodName]=\"col.formElem.apiMethod || ''\"\r\n [configMethodName]=\"col.formElem.apiConfigMethod || ''\"\r\n [service]=\"col.formElem.apiService\"\r\n [object]=\"col.formElem.objectName ? row[col.formElem.objectName] : null\"\r\n [isLister]=\"!!col.formElem.isListerAutocomplete\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? onSelectChange(col, row, i, $event) : null\">\r\n </osl-autocomplete>\r\n }\r\n\r\n @case ('radio') {\r\n <osl-radio\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [datasource]=\"col.formElem.datasource || []\"\r\n [displayField]=\"col.formElem.displayField || ''\"\r\n [valueField]=\"col.formElem.valueField || ''\"\r\n [inline]=\"!!col.formElem.inline\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-radio>\r\n }\r\n\r\n @case ('checkbox') {\r\n <osl-checkbox\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [indeterminate]=\"!!col.formElem.indeterminate\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-checkbox>\r\n }\r\n\r\n @case ('slide-toggle') {\r\n <osl-slide-toggle\r\n [label]=\"''\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [labelPosition]=\"col.formElem.labelPosition || 'after'\"\r\n [trueLabel]=\"col.formElem.trueLabel || ''\"\r\n [falseLabel]=\"col.formElem.falseLabel || ''\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-slide-toggle>\r\n }\r\n\r\n @case ('datepicker') {\r\n <osl-datepicker\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [dateType]=\"col.formElem.dateType || 'date'\"\r\n [placeholder]=\"col.formElem.placeholder || col.displayName\"\r\n [minDate]=\"col.formElem.minDate || ''\"\r\n [maxDate]=\"col.formElem.maxDate || ''\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-datepicker>\r\n }\r\n\r\n @case ('file-uploader') {\r\n <osl-file-upload\r\n [label]=\"''\"\r\n [required]=\"false\"\r\n [disabled]=\"isDisabled(col.formElem,row,i)\"\r\n [accept]=\"col.formElem.accept || ''\"\r\n [multiple]=\"!!col.formElem.multiple\"\r\n [maxSize]=\"col.formElem.maxFileSize || 0\"\r\n [model]=\"row[col.key]\"\r\n (modelChange)=\"onCellChange(row, col, $event)\"\r\n (changeEv)=\"col.formElem.change ? col.formElem.change(row,i) : null\">\r\n </osl-file-upload>\r\n }\r\n\r\n }\r\n </div>\r\n } @else {\r\n <span class=\"osl-fg-cell-text\">{{ row[col.key] ?? '--' }}</span>\r\n }\r\n </td>\r\n }\r\n\r\n </tr>\r\n }\r\n }\r\n\r\n </tbody>\r\n\r\n @if (footerColumns && footerColumns.length > 0 && datasource && datasource.length > 0) {\r\n <tfoot class=\"osl-fg-tfoot\">\r\n <tr class=\"osl-fg-footer-row\">\r\n @for (fcol of footerColumns; track $index) {\r\n <td\r\n [class]=\"'osl-fg-footer-td ' + (fcol.class || '')\"\r\n [attr.colspan]=\"fcol.colspan || 1\">\r\n {{ fcol.displayFn ? fcol.displayFn(datasource) : (fcol.display || '') }}\r\n </td>\r\n }\r\n </tr>\r\n </tfoot>\r\n }\r\n\r\n </table>\r\n </div>\r\n\r\n @if (isPaginated) {\r\n <div class=\"osl-fg-pagination\">\r\n\r\n <span class=\"osl-fg-pagination__info\">\r\n @if (loading) {\r\n Loading...\r\n } @else if (_total > 0) {\r\n {{ startRecord }}\u2013{{ endRecord }} of {{ _total }} rows\r\n } @else {\r\n No rows\r\n }\r\n </span>\r\n\r\n <div class=\"osl-fg-pagination__controls\">\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage === 1 || loading\">\u00AB</button>\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage - 1)\" [disabled]=\"currentPage === 1 || loading\">\u2039 Prev</button>\r\n\r\n @for (page of pageNumbers; track $index) {\r\n @if (page === -1) {\r\n <span class=\"osl-fg-page-ellipsis\">\u2026</span>\r\n } @else {\r\n <button\r\n class=\"osl-fg-page-btn osl-fg-page-btn--num\"\r\n [class.osl-fg-page-btn--active]=\"page === currentPage\"\r\n [disabled]=\"loading\"\r\n (click)=\"goToPage(page)\">\r\n {{ page }}\r\n </button>\r\n }\r\n }\r\n\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(currentPage + 1)\" [disabled]=\"currentPage === totalPages || loading\">Next \u203A</button>\r\n <button class=\"osl-fg-page-btn osl-fg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage === totalPages || loading\">\u00BB</button>\r\n </div>\r\n\r\n <div class=\"osl-fg-pagination__size\">\r\n <select class=\"osl-fg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\r\n @for (opt of pageSizeOptions; track opt) {\r\n <option [value]=\"opt\">{{ opt }} per page</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n </div>\r\n }\r\n\r\n</div>\r\n", styles: [".osl-fg-wrapper{display:flex;flex-direction:column;width:100%;font-size:var(--osl-text-font-size);font-family:inherit;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 8px);overflow:hidden;background:#fff}.osl-fg-table-container{overflow-x:auto;overflow-y:auto;min-height:120px}.osl-fg-table{width:100%;border-collapse:collapse;table-layout:auto}.osl-fg-thead{position:sticky;top:0;z-index:2}.osl-fg-th{background:#f3f4f6;color:#4b5563;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;padding:10px 12px;text-align:left;border-bottom:2px solid var(--osl-border-color, #e5e7eb);border-right:1px solid var(--osl-border-color, #e5e7eb);white-space:nowrap;vertical-align:middle}.osl-fg-th:last-child{border-right:none}.osl-fg-th--actions{width:44px;min-width:44px;text-align:center;padding:6px}.osl-fg-th-required{color:#ef4444;margin-left:2px;font-size:14px;line-height:1;vertical-align:middle}.osl-fg-row{border-bottom:1px solid #f0f0f0;transition:background .1s}.osl-fg-row:nth-child(odd){background:#f8fafc}.osl-fg-row:nth-child(2n){background:#fff}.osl-fg-row:hover{background:#eef2ff!important}.osl-fg-row:last-child{border-bottom:none}.osl-fg-row--skeleton{pointer-events:none}.osl-fg-td{padding:4px 10px;vertical-align:middle;border-right:1px solid #f0f0f0}.osl-fg-td:last-child{border-right:none}.osl-fg-td--actions{width:44px;min-width:44px;text-align:center;padding:4px 6px}.osl-fg-cell-form{min-width:100px}.osl-fg-cell-form ::ng-deep .mat-mdc-form-field-subscript-wrapper{display:none!important}.osl-fg-cell-form ::ng-deep .mat-mdc-form-field,.osl-fg-cell-form ::ng-deep mat-form-field{width:100%}.osl-fg-cell-form ::ng-deep .mat-mdc-text-field-wrapper{padding-top:0}.osl-fg-cell-text{color:#111827;font-size:var(--osl-text-font-size)}.osl-fg-add-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;border-radius:6px;background:var(--osl-primary, #6366f1);color:#fff;cursor:pointer;padding:0;box-shadow:0 1px 4px #6366f159;transition:background .15s,box-shadow .15s,transform .1s}.osl-fg-add-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}.osl-fg-add-btn:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);box-shadow:0 2px 8px #6366f173;transform:scale(1.05)}.osl-fg-add-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}.osl-fg-delete-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1.5px solid transparent;border-radius:6px;background:transparent;color:#d1d5db;cursor:pointer;padding:0;transition:border-color .15s,color .15s,background .15s}.osl-fg-delete-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.osl-fg-delete-btn:hover{border-color:#fca5a5;color:#ef4444;background:#fef2f2}.osl-fg-empty{padding:40px 16px;text-align:center}.osl-fg-empty-inner{display:flex;flex-direction:column;align-items:center;gap:10px;color:#9ca3af}.osl-fg-empty-inner svg{opacity:.4}.osl-fg-empty-inner p{margin:0;font-size:13px}@keyframes osl-fg-pulse{0%,to{opacity:1}50%{opacity:.4}}.osl-fg-skeleton{height:36px;border-radius:6px;background:#e5e7eb;animation:osl-fg-pulse 1.4s ease-in-out infinite}.osl-fg-skeleton--sm{height:28px;width:28px;border-radius:4px;margin:0 auto}.osl-fg-tfoot{position:sticky;bottom:0;z-index:2}.osl-fg-footer-td{background:#f3f4f6;color:#4b5563;font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.04em;padding:10px 12px;text-align:left;border-top:2px solid var(--osl-border-color, #e5e7eb);border-right:1px solid var(--osl-border-color, #e5e7eb);white-space:nowrap;vertical-align:middle}.osl-fg-footer-td:last-child{border-right:none}.osl-fg-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-top:1px solid var(--osl-border-color, #e5e7eb);background:#f9fafb;flex-shrink:0;flex-wrap:wrap}.osl-fg-pagination__info{font-size:12px;color:#6b7280;white-space:nowrap;min-width:120px}.osl-fg-pagination__controls{display:flex;align-items:center;gap:3px;flex-wrap:nowrap}.osl-fg-pagination__size{display:flex;align-items:center;justify-content:flex-end;min-width:120px}.osl-fg-page-btn{display:inline-flex;align-items:center;justify-content:center;height:30px;padding:0 10px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:13px;font-family:inherit;cursor:pointer;transition:background .12s,border-color .12s,color .12s;white-space:nowrap}.osl-fg-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af}.osl-fg-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-fg-page-btn--nav{font-size:12px;color:#6b7280}.osl-fg-page-btn--num{min-width:30px;padding:0;font-size:13px}.osl-fg-page-btn--active{background:var(--osl-primary, #6366f1);border-color:var(--osl-primary, #6366f1);color:#fff;font-weight:600}.osl-fg-page-btn--active:hover:not(:disabled){background:var(--osl-primary-hover, #4f46e5);border-color:var(--osl-primary-hover, #4f46e5)}.osl-fg-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-fg-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:var(--osl-border-radius, 6px);background:#fff;color:#374151;font-size:12px;font-family:inherit;cursor:pointer;outline:none}.osl-fg-page-size:focus{border-color:var(--osl-primary, #6366f1)}.osl-fg-page-size:disabled{opacity:.4;cursor:not-allowed}.osl-grid-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1.5px solid var(--osl-border-color);border-radius:var(--osl-border-radius);background:transparent;color:#6b7280;cursor:pointer;padding:0;transition:border-color .15s,color .15s,background .15s;margin:0 2px}.osl-grid-icon-btn mat-icon{font-size:16px;width:16px;height:16px;line-height:16px}.osl-grid-icon-btn:hover{border-color:var(--osl-primary);color:var(--osl-primary);background:#6366f10f}.osl-grid-icon-btn--danger:hover{border-color:#ef4444;color:#ef4444;background:#ef44440f}\n"], dependencies: [{ kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: Oslinput, selector: "osl-input", inputs: ["label", "required", "disabled", "model", "type", "placeholder", "mask", "min", "max", "minLength", "maxLength", "prefixIcon", "suffixIcon", "skeletonLoading", "skeletonTheme", "onlyChars", "isCapitalize", "decimalPortion"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: Osltextarea, selector: "osl-textarea", inputs: ["label", "rows", "required", "disabled", "model", "placeholder", "maxLength", "minLength", "characterCounter", "resize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSelect, selector: "osl-select", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "clearable", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslRadio, selector: "osl-radio", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "inline", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslSlideToggle, selector: "osl-slide-toggle", inputs: ["label", "disabled", "model", "labelPosition", "trueLabel", "falseLabel", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslAutocomplete, selector: "osl-autocomplete", inputs: ["label", "required", "disabled", "model", "datasource", "displayField", "valueField", "placeholder", "loading", "searchType", "methodName", "configMethodName", "service", "object", "skeletonLoading", "skeletonTheme", "isLister"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslFileUpload, selector: "osl-file-upload", inputs: ["label", "required", "disabled", "model", "accept", "multiple", "maxSize", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslDatepicker, selector: "osl-datepicker", inputs: ["label", "required", "disabled", "model", "dateType", "placeholder", "minDate", "maxDate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }, { kind: "component", type: OslCheckbox, selector: "osl-checkbox", inputs: ["label", "disabled", "required", "model", "indeterminate", "skeletonLoading", "skeletonTheme"], outputs: ["modelChange", "changeEv"] }] });
|
|
2810
2823
|
}
|
|
2811
2824
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslFormGrid, decorators: [{
|
|
2812
2825
|
type: Component,
|
|
@@ -2922,6 +2935,818 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2922
2935
|
}]
|
|
2923
2936
|
}] });
|
|
2924
2937
|
|
|
2938
|
+
// ─── Component ────────────────────────────────────────────────────────────────
|
|
2939
|
+
class OslReportGrid {
|
|
2940
|
+
// Inputs
|
|
2941
|
+
columns = [];
|
|
2942
|
+
datasource = [];
|
|
2943
|
+
loading = false;
|
|
2944
|
+
totalRecords = 0;
|
|
2945
|
+
autoMode = true;
|
|
2946
|
+
isPaginated = true;
|
|
2947
|
+
pageSize = 50;
|
|
2948
|
+
tableHeight = 'calc(100vh - 220px)';
|
|
2949
|
+
striped = true;
|
|
2950
|
+
exportable = true;
|
|
2951
|
+
rowHeight = 40;
|
|
2952
|
+
rowSelection = null;
|
|
2953
|
+
showAggregates = false;
|
|
2954
|
+
title = '';
|
|
2955
|
+
// Outputs
|
|
2956
|
+
pageChange = new EventEmitter();
|
|
2957
|
+
pageSizeChange = new EventEmitter();
|
|
2958
|
+
sortChange = new EventEmitter();
|
|
2959
|
+
rowClick = new EventEmitter();
|
|
2960
|
+
selectionChange = new EventEmitter();
|
|
2961
|
+
headerScrollRef;
|
|
2962
|
+
bodyScrollRef;
|
|
2963
|
+
cdr = inject(ChangeDetectorRef);
|
|
2964
|
+
datePipe = inject(DatePipe);
|
|
2965
|
+
decimalPipe = inject(DecimalPipe);
|
|
2966
|
+
// ── Column state ─────────────────────────────────────────────────────────────
|
|
2967
|
+
_cols = [];
|
|
2968
|
+
_visibleCols = [];
|
|
2969
|
+
_totalWidth = 0;
|
|
2970
|
+
get visibleCols() { return this._visibleCols; }
|
|
2971
|
+
get totalWidth() { return this._totalWidth; }
|
|
2972
|
+
get pinnedWidth() {
|
|
2973
|
+
return this._visibleCols.filter(c => c._pinned).reduce((s, c) => s + c._width, 0);
|
|
2974
|
+
}
|
|
2975
|
+
updateVisibleCols() {
|
|
2976
|
+
this._visibleCols = this._cols.filter(c => !c._hidden);
|
|
2977
|
+
this._totalWidth = this._visibleCols.reduce((s, c) => s + c._width, 0)
|
|
2978
|
+
+ (this.rowSelection === 'multiple' ? 36 : 0);
|
|
2979
|
+
}
|
|
2980
|
+
// ── Sort state ────────────────────────────────────────────────────────────────
|
|
2981
|
+
sortStates = new Map();
|
|
2982
|
+
sortCounter = 0;
|
|
2983
|
+
// ── Filter / Search state ─────────────────────────────────────────────────────
|
|
2984
|
+
globalSearch = '';
|
|
2985
|
+
columnSearch = {};
|
|
2986
|
+
excelFilters = {};
|
|
2987
|
+
excelFilterState = {};
|
|
2988
|
+
openFilterKey = null;
|
|
2989
|
+
filterDropdownPos = { top: 0, left: 0 };
|
|
2990
|
+
// ── Group state ───────────────────────────────────────────────────────────────
|
|
2991
|
+
activeGroups = [];
|
|
2992
|
+
collapsedPaths = new Set();
|
|
2993
|
+
showGroupPanel = true;
|
|
2994
|
+
// ── Flat rows (computed) ──────────────────────────────────────────────────────
|
|
2995
|
+
flatRows = [];
|
|
2996
|
+
_filteredTotal = 0;
|
|
2997
|
+
// ── Pagination ────────────────────────────────────────────────────────────────
|
|
2998
|
+
currentPage = 1;
|
|
2999
|
+
pageSizeOptions = [25, 50, 100, 200, 500];
|
|
3000
|
+
// ── UI state ──────────────────────────────────────────────────────────────────
|
|
3001
|
+
showColumnConfig = false;
|
|
3002
|
+
selectedRows = new Set();
|
|
3003
|
+
columnMenuKey = null;
|
|
3004
|
+
columnMenuPos = { top: 0, left: 0 };
|
|
3005
|
+
copiedCell = null;
|
|
3006
|
+
resizing = null;
|
|
3007
|
+
_suppressNextHeaderClick = false;
|
|
3008
|
+
_columnDragJustEnded = false;
|
|
3009
|
+
// ─── Lifecycle ────────────────────────────────────────────────────────────────
|
|
3010
|
+
ngOnChanges(changes) {
|
|
3011
|
+
if (changes['columns']) {
|
|
3012
|
+
this.initColumns();
|
|
3013
|
+
this.excelFilterState = {};
|
|
3014
|
+
}
|
|
3015
|
+
if (changes['datasource'] || changes['columns']) {
|
|
3016
|
+
this.currentPage = 1;
|
|
3017
|
+
this.processData();
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
initColumns() {
|
|
3021
|
+
this._cols = this.columns.map(col => ({
|
|
3022
|
+
...col,
|
|
3023
|
+
_width: col.width ?? 150,
|
|
3024
|
+
_hidden: col.hidden ?? false,
|
|
3025
|
+
_pinned: col.pinned ?? false,
|
|
3026
|
+
_stickyLeft: 0,
|
|
3027
|
+
sortable: col.sortable !== false,
|
|
3028
|
+
filterable: col.filterable !== false,
|
|
3029
|
+
searchable: col.searchable !== false,
|
|
3030
|
+
groupable: col.groupable !== false,
|
|
3031
|
+
resizable: col.resizable !== false,
|
|
3032
|
+
minWidth: col.minWidth ?? 60,
|
|
3033
|
+
align: col.align ?? 'left',
|
|
3034
|
+
}));
|
|
3035
|
+
this.recomputePinnedOffsets();
|
|
3036
|
+
this.updateVisibleCols();
|
|
3037
|
+
}
|
|
3038
|
+
recomputePinnedOffsets() {
|
|
3039
|
+
let offset = this.rowSelection === 'multiple' ? 36 : 0;
|
|
3040
|
+
for (const col of this._cols) {
|
|
3041
|
+
if (!col._hidden && col._pinned) {
|
|
3042
|
+
col._stickyLeft = offset;
|
|
3043
|
+
offset += col._width;
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
// ─── Data pipeline ────────────────────────────────────────────────────────────
|
|
3048
|
+
getFilteredRows() {
|
|
3049
|
+
let rows = [...this.datasource];
|
|
3050
|
+
if (this.globalSearch.trim()) {
|
|
3051
|
+
const term = this.globalSearch.toLowerCase();
|
|
3052
|
+
rows = rows.filter(row => this.visibleCols.some(col => String(this.getCellDisplay(row, col)).toLowerCase().includes(term)));
|
|
3053
|
+
}
|
|
3054
|
+
for (const col of this._cols) {
|
|
3055
|
+
const term = (this.columnSearch[col.key] ?? '').trim().toLowerCase();
|
|
3056
|
+
if (term) {
|
|
3057
|
+
rows = rows.filter(row => String(this.getCellDisplay(row, col)).toLowerCase().includes(term));
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
for (const [key, activeSet] of Object.entries(this.excelFilters)) {
|
|
3061
|
+
if (activeSet.size > 0) {
|
|
3062
|
+
rows = rows.filter(row => activeSet.has(row[key]));
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
return rows;
|
|
3066
|
+
}
|
|
3067
|
+
processData() {
|
|
3068
|
+
let rows = this.getFilteredRows();
|
|
3069
|
+
if (this.autoMode && this.sortStates.size > 0) {
|
|
3070
|
+
const sorts = [...this.sortStates.values()].sort((a, b) => a.index - b.index);
|
|
3071
|
+
rows.sort((a, b) => {
|
|
3072
|
+
for (const s of sorts) {
|
|
3073
|
+
const cmp = String(a[s.key] ?? '').localeCompare(String(b[s.key] ?? ''), undefined, { numeric: true });
|
|
3074
|
+
if (cmp !== 0)
|
|
3075
|
+
return s.asc ? cmp : -cmp;
|
|
3076
|
+
}
|
|
3077
|
+
return 0;
|
|
3078
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
this._filteredTotal = rows.length;
|
|
3081
|
+
if (this.activeGroups.length > 0) {
|
|
3082
|
+
// Group ALL filtered rows first, then paginate the flat list so every
|
|
3083
|
+
// group is computed across the full dataset, not just one page's slice.
|
|
3084
|
+
const allGrouped = this.flattenGroups(rows, this.activeGroups, 0, '');
|
|
3085
|
+
if (this.isPaginated && this.autoMode) {
|
|
3086
|
+
const start = (this.currentPage - 1) * this.pageSize;
|
|
3087
|
+
this.flatRows = allGrouped.slice(start, start + this.pageSize);
|
|
3088
|
+
}
|
|
3089
|
+
else {
|
|
3090
|
+
this.flatRows = allGrouped;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
else if (this.isPaginated && this.autoMode) {
|
|
3094
|
+
const start = (this.currentPage - 1) * this.pageSize;
|
|
3095
|
+
this.flatRows = rows.slice(start, start + this.pageSize)
|
|
3096
|
+
.map((r, i) => ({ _type: 'data', _data: r, _rowIndex: i }));
|
|
3097
|
+
}
|
|
3098
|
+
else {
|
|
3099
|
+
this.flatRows = rows.map((r, i) => ({ _type: 'data', _data: r, _rowIndex: i }));
|
|
3100
|
+
}
|
|
3101
|
+
this.cdr.markForCheck();
|
|
3102
|
+
}
|
|
3103
|
+
flattenGroups(rows, groupKeys, level, parentPath) {
|
|
3104
|
+
const key = groupKeys[0];
|
|
3105
|
+
const remaining = groupKeys.slice(1);
|
|
3106
|
+
const groups = new Map();
|
|
3107
|
+
for (const row of rows) {
|
|
3108
|
+
const val = row[key];
|
|
3109
|
+
if (!groups.has(val))
|
|
3110
|
+
groups.set(val, []);
|
|
3111
|
+
groups.get(val).push(row);
|
|
3112
|
+
}
|
|
3113
|
+
const col = this._cols.find(c => c.key === key);
|
|
3114
|
+
const result = [];
|
|
3115
|
+
for (const [val, groupRows] of groups) {
|
|
3116
|
+
const path = parentPath ? `${parentPath}||${key}=${val}` : `${key}=${val}`;
|
|
3117
|
+
const expanded = !this.collapsedPaths.has(path);
|
|
3118
|
+
result.push({
|
|
3119
|
+
_type: 'group', _key: key, _value: val,
|
|
3120
|
+
_label: this.getCellDisplayByKey(val, col, undefined),
|
|
3121
|
+
_colLabel: col?.label ?? key,
|
|
3122
|
+
_level: level, _count: groupRows.length,
|
|
3123
|
+
_expanded: expanded, _path: path,
|
|
3124
|
+
});
|
|
3125
|
+
if (expanded) {
|
|
3126
|
+
if (remaining.length > 0) {
|
|
3127
|
+
result.push(...this.flattenGroups(groupRows, remaining, level + 1, path));
|
|
3128
|
+
}
|
|
3129
|
+
else {
|
|
3130
|
+
groupRows.forEach((r, i) => result.push({ _type: 'data', _data: r, _rowIndex: i }));
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
return result;
|
|
3135
|
+
}
|
|
3136
|
+
// ─── Sort ─────────────────────────────────────────────────────────────────────
|
|
3137
|
+
onHeaderClick(col, event) {
|
|
3138
|
+
if (!col.sortable)
|
|
3139
|
+
return;
|
|
3140
|
+
if (this._suppressNextHeaderClick || this._columnDragJustEnded)
|
|
3141
|
+
return;
|
|
3142
|
+
const existing = this.sortStates.get(col.key);
|
|
3143
|
+
if (event.shiftKey) {
|
|
3144
|
+
if (existing) {
|
|
3145
|
+
existing.asc ? this.sortStates.set(col.key, { ...existing, asc: false })
|
|
3146
|
+
: this.sortStates.delete(col.key);
|
|
3147
|
+
}
|
|
3148
|
+
else {
|
|
3149
|
+
this.sortStates.set(col.key, { key: col.key, asc: true, index: this.sortCounter++ });
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
else {
|
|
3153
|
+
this.sortStates.clear();
|
|
3154
|
+
this.sortStates.set(col.key, { key: col.key, asc: existing ? !existing.asc : true, index: 0 });
|
|
3155
|
+
this.sortCounter = 1;
|
|
3156
|
+
}
|
|
3157
|
+
this.sortChange.emit({ sorts: [...this.sortStates.values()].map(s => ({ key: s.key, asc: s.asc })) });
|
|
3158
|
+
this.processData();
|
|
3159
|
+
}
|
|
3160
|
+
getSortState(key) { return this.sortStates.get(key); }
|
|
3161
|
+
clearSort() { this.sortStates.clear(); this.processData(); }
|
|
3162
|
+
// ─── Column search ────────────────────────────────────────────────────────────
|
|
3163
|
+
onColumnSearch(key, value) {
|
|
3164
|
+
this.columnSearch[key] = value;
|
|
3165
|
+
this.currentPage = 1;
|
|
3166
|
+
this.processData();
|
|
3167
|
+
}
|
|
3168
|
+
// ─── Excel filter ─────────────────────────────────────────────────────────────
|
|
3169
|
+
openExcelFilter(key, event) {
|
|
3170
|
+
event.stopPropagation();
|
|
3171
|
+
if (this.openFilterKey === key) {
|
|
3172
|
+
this.openFilterKey = null;
|
|
3173
|
+
return;
|
|
3174
|
+
}
|
|
3175
|
+
if (!this.excelFilterState[key])
|
|
3176
|
+
this.buildExcelFilterItems(key);
|
|
3177
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
3178
|
+
this.filterDropdownPos = {
|
|
3179
|
+
top: rect.bottom + 4,
|
|
3180
|
+
left: Math.max(4, Math.min(rect.left, window.innerWidth - 264)),
|
|
3181
|
+
};
|
|
3182
|
+
this.openFilterKey = key;
|
|
3183
|
+
}
|
|
3184
|
+
buildExcelFilterItems(key) {
|
|
3185
|
+
const activeSet = this.excelFilters[key] ?? new Set();
|
|
3186
|
+
const col = this._cols.find(c => c.key === key);
|
|
3187
|
+
const seen = new Map();
|
|
3188
|
+
for (const row of this.datasource) {
|
|
3189
|
+
const val = row[key];
|
|
3190
|
+
if (!seen.has(val))
|
|
3191
|
+
seen.set(val, this.getCellDisplayByKey(val, col, undefined));
|
|
3192
|
+
}
|
|
3193
|
+
this.excelFilterState[key] = {
|
|
3194
|
+
search: '',
|
|
3195
|
+
allItems: [...seen.entries()].map(([value, label]) => ({
|
|
3196
|
+
value, label,
|
|
3197
|
+
checked: activeSet.size === 0 || activeSet.has(value),
|
|
3198
|
+
})),
|
|
3199
|
+
};
|
|
3200
|
+
}
|
|
3201
|
+
getFilteredExcelItems(key) {
|
|
3202
|
+
const state = this.excelFilterState[key];
|
|
3203
|
+
if (!state)
|
|
3204
|
+
return [];
|
|
3205
|
+
const s = state.search.toLowerCase();
|
|
3206
|
+
return s ? state.allItems.filter(i => i.label.toLowerCase().includes(s)) : state.allItems;
|
|
3207
|
+
}
|
|
3208
|
+
isAllExcelChecked(key) {
|
|
3209
|
+
const state = this.excelFilterState[key];
|
|
3210
|
+
return !!state && state.allItems.every(i => i.checked);
|
|
3211
|
+
}
|
|
3212
|
+
toggleAllExcelFilter(key, checked) {
|
|
3213
|
+
this.excelFilterState[key]?.allItems.forEach(i => (i.checked = checked));
|
|
3214
|
+
}
|
|
3215
|
+
applyExcelFilter(key) {
|
|
3216
|
+
const state = this.excelFilterState[key];
|
|
3217
|
+
if (!state)
|
|
3218
|
+
return;
|
|
3219
|
+
const checked = state.allItems.filter(i => i.checked).map(i => i.value);
|
|
3220
|
+
if (checked.length === state.allItems.length) {
|
|
3221
|
+
delete this.excelFilters[key];
|
|
3222
|
+
}
|
|
3223
|
+
else {
|
|
3224
|
+
this.excelFilters[key] = new Set(checked);
|
|
3225
|
+
}
|
|
3226
|
+
this.openFilterKey = null;
|
|
3227
|
+
this.currentPage = 1;
|
|
3228
|
+
this.processData();
|
|
3229
|
+
}
|
|
3230
|
+
clearExcelFilter(key, event) {
|
|
3231
|
+
event?.stopPropagation();
|
|
3232
|
+
delete this.excelFilters[key];
|
|
3233
|
+
if (this.excelFilterState[key])
|
|
3234
|
+
this.excelFilterState[key].allItems.forEach(i => (i.checked = true));
|
|
3235
|
+
this.processData();
|
|
3236
|
+
}
|
|
3237
|
+
hasActiveFilter(key) {
|
|
3238
|
+
return !!this.excelFilters[key] && this.excelFilters[key].size > 0;
|
|
3239
|
+
}
|
|
3240
|
+
clearAllFilters() {
|
|
3241
|
+
this.excelFilters = {};
|
|
3242
|
+
this.excelFilterState = {};
|
|
3243
|
+
this.columnSearch = {};
|
|
3244
|
+
this.globalSearch = '';
|
|
3245
|
+
this.currentPage = 1;
|
|
3246
|
+
this.processData();
|
|
3247
|
+
}
|
|
3248
|
+
get hasAnyFilter() {
|
|
3249
|
+
return !!this.globalSearch.trim()
|
|
3250
|
+
|| Object.values(this.columnSearch).some(v => !!v?.trim())
|
|
3251
|
+
|| Object.values(this.excelFilters).some(s => s.size > 0);
|
|
3252
|
+
}
|
|
3253
|
+
// ─── Grouping ─────────────────────────────────────────────────────────────────
|
|
3254
|
+
addGroup(key) {
|
|
3255
|
+
if (!this.activeGroups.includes(key)) {
|
|
3256
|
+
this.activeGroups.push(key);
|
|
3257
|
+
this.collapsedPaths.clear();
|
|
3258
|
+
this.processData();
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
removeGroup(key) {
|
|
3262
|
+
this.activeGroups = this.activeGroups.filter(k => k !== key);
|
|
3263
|
+
this.collapsedPaths.clear();
|
|
3264
|
+
this.processData();
|
|
3265
|
+
}
|
|
3266
|
+
clearGroups() { this.activeGroups = []; this.collapsedPaths.clear(); this.processData(); }
|
|
3267
|
+
toggleGroup(path) {
|
|
3268
|
+
this.collapsedPaths.has(path) ? this.collapsedPaths.delete(path) : this.collapsedPaths.add(path);
|
|
3269
|
+
this.processData();
|
|
3270
|
+
}
|
|
3271
|
+
expandAll() { this.collapsedPaths.clear(); this.processData(); }
|
|
3272
|
+
collapseAll() {
|
|
3273
|
+
this.flatRows
|
|
3274
|
+
.filter((r) => r._type === 'group' && r._level === 0)
|
|
3275
|
+
.forEach(r => this.collapsedPaths.add(r._path));
|
|
3276
|
+
this.processData();
|
|
3277
|
+
}
|
|
3278
|
+
onGroupPanelDrop(event) {
|
|
3279
|
+
moveItemInArray(this.activeGroups, event.previousIndex, event.currentIndex);
|
|
3280
|
+
this.collapsedPaths.clear();
|
|
3281
|
+
this.processData();
|
|
3282
|
+
}
|
|
3283
|
+
getGroupColLabel(key) {
|
|
3284
|
+
return this._cols.find(c => c.key === key)?.label ?? key;
|
|
3285
|
+
}
|
|
3286
|
+
// ─── Column config ────────────────────────────────────────────────────────────
|
|
3287
|
+
toggleColumnVisibility(col) {
|
|
3288
|
+
col._hidden = !col._hidden;
|
|
3289
|
+
this.recomputePinnedOffsets();
|
|
3290
|
+
this.updateVisibleCols();
|
|
3291
|
+
this.processData();
|
|
3292
|
+
}
|
|
3293
|
+
toggleColumnPin(col) {
|
|
3294
|
+
col._pinned = !col._pinned;
|
|
3295
|
+
const pinned = this._cols.filter(c => c._pinned);
|
|
3296
|
+
const unpinned = this._cols.filter(c => !c._pinned);
|
|
3297
|
+
this._cols = [...pinned, ...unpinned];
|
|
3298
|
+
this.recomputePinnedOffsets();
|
|
3299
|
+
this.updateVisibleCols();
|
|
3300
|
+
this.closeColumnMenu();
|
|
3301
|
+
this.cdr.markForCheck();
|
|
3302
|
+
}
|
|
3303
|
+
// Header drag: CDK indices are relative to visibleCols DOM order
|
|
3304
|
+
onColumnReorder(event) {
|
|
3305
|
+
if (event.previousIndex === event.currentIndex)
|
|
3306
|
+
return;
|
|
3307
|
+
const fromCol = this._visibleCols[event.previousIndex];
|
|
3308
|
+
const toCol = this._visibleCols[event.currentIndex];
|
|
3309
|
+
if (!fromCol || !toCol)
|
|
3310
|
+
return;
|
|
3311
|
+
const fromIdx = this._cols.indexOf(fromCol);
|
|
3312
|
+
const toIdx = this._cols.indexOf(toCol);
|
|
3313
|
+
if (fromIdx === -1 || toIdx === -1)
|
|
3314
|
+
return;
|
|
3315
|
+
moveItemInArray(this._cols, fromIdx, toIdx);
|
|
3316
|
+
this.recomputePinnedOffsets();
|
|
3317
|
+
this.updateVisibleCols();
|
|
3318
|
+
this.cdr.markForCheck();
|
|
3319
|
+
}
|
|
3320
|
+
// Config panel drag: CDK indices are relative to _cols directly
|
|
3321
|
+
onConfigColumnReorder(event) {
|
|
3322
|
+
if (event.previousIndex === event.currentIndex)
|
|
3323
|
+
return;
|
|
3324
|
+
moveItemInArray(this._cols, event.previousIndex, event.currentIndex);
|
|
3325
|
+
this.recomputePinnedOffsets();
|
|
3326
|
+
this.updateVisibleCols();
|
|
3327
|
+
this.cdr.markForCheck();
|
|
3328
|
+
}
|
|
3329
|
+
autoSizeColumn(col, event) {
|
|
3330
|
+
event?.stopPropagation();
|
|
3331
|
+
const labelPx = col.label.length * 8 + 48;
|
|
3332
|
+
const sample = this.datasource.slice(0, 200);
|
|
3333
|
+
const maxDataPx = sample.reduce((max, row) => {
|
|
3334
|
+
const len = String(this.getCellDisplay(row, col)).length * 7 + 24;
|
|
3335
|
+
return Math.max(max, len);
|
|
3336
|
+
}, 0);
|
|
3337
|
+
col._width = Math.max(col.minWidth ?? 60, Math.min(Math.max(labelPx, maxDataPx), 420));
|
|
3338
|
+
this.recomputePinnedOffsets();
|
|
3339
|
+
this.updateVisibleCols();
|
|
3340
|
+
this.closeColumnMenu();
|
|
3341
|
+
this.cdr.markForCheck();
|
|
3342
|
+
}
|
|
3343
|
+
autoSizeAll() { this.visibleCols.forEach(c => this.autoSizeColumn(c)); }
|
|
3344
|
+
// ─── Column menu ──────────────────────────────────────────────────────────────
|
|
3345
|
+
openColumnMenu(key, event) {
|
|
3346
|
+
event.stopPropagation();
|
|
3347
|
+
this.columnMenuKey = this.columnMenuKey === key ? null : key;
|
|
3348
|
+
if (this.columnMenuKey) {
|
|
3349
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
3350
|
+
this.columnMenuPos = { top: rect.bottom + 4, left: Math.max(rect.left - 100, 4) };
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
closeColumnMenu() { this.columnMenuKey = null; }
|
|
3354
|
+
getColumnByKey(key) {
|
|
3355
|
+
return this._cols.find(c => c.key === key);
|
|
3356
|
+
}
|
|
3357
|
+
// ─── Column resize ────────────────────────────────────────────────────────────
|
|
3358
|
+
startResize(col, event) {
|
|
3359
|
+
event.stopPropagation();
|
|
3360
|
+
event.preventDefault();
|
|
3361
|
+
this.resizing = { key: col.key, startX: event.clientX, startWidth: col._width };
|
|
3362
|
+
}
|
|
3363
|
+
onMouseMove(event) {
|
|
3364
|
+
if (!this.resizing)
|
|
3365
|
+
return;
|
|
3366
|
+
const col = this._cols.find(c => c.key === this.resizing.key);
|
|
3367
|
+
if (!col)
|
|
3368
|
+
return;
|
|
3369
|
+
col._width = Math.max(col.minWidth ?? 60, this.resizing.startWidth + (event.clientX - this.resizing.startX));
|
|
3370
|
+
this.recomputePinnedOffsets();
|
|
3371
|
+
this._totalWidth = this._visibleCols.reduce((s, c) => s + c._width, 0)
|
|
3372
|
+
+ (this.rowSelection === 'multiple' ? 36 : 0);
|
|
3373
|
+
this.cdr.markForCheck();
|
|
3374
|
+
}
|
|
3375
|
+
onMouseUp() {
|
|
3376
|
+
if (this.resizing) {
|
|
3377
|
+
this._suppressNextHeaderClick = true;
|
|
3378
|
+
setTimeout(() => { this._suppressNextHeaderClick = false; }, 100);
|
|
3379
|
+
}
|
|
3380
|
+
this.resizing = null;
|
|
3381
|
+
this.cdr.markForCheck();
|
|
3382
|
+
}
|
|
3383
|
+
onColumnDragStarted() { this._columnDragJustEnded = false; }
|
|
3384
|
+
onColumnDragEnded() {
|
|
3385
|
+
this._columnDragJustEnded = true;
|
|
3386
|
+
setTimeout(() => { this._columnDragJustEnded = false; }, 100);
|
|
3387
|
+
}
|
|
3388
|
+
showAllColumns() {
|
|
3389
|
+
this._cols.forEach(c => (c._hidden = false));
|
|
3390
|
+
this.recomputePinnedOffsets();
|
|
3391
|
+
this.updateVisibleCols();
|
|
3392
|
+
this.cdr.markForCheck();
|
|
3393
|
+
}
|
|
3394
|
+
// ─── Header scroll sync ───────────────────────────────────────────────────────
|
|
3395
|
+
onBodyScroll(event) {
|
|
3396
|
+
const scrollLeft = event.target.scrollLeft;
|
|
3397
|
+
if (this.headerScrollRef) {
|
|
3398
|
+
this.headerScrollRef.nativeElement.scrollLeft = scrollLeft;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
// ─── Row selection ────────────────────────────────────────────────────────────
|
|
3402
|
+
onRowClick(row, event) {
|
|
3403
|
+
this.rowClick.emit(row);
|
|
3404
|
+
if (!this.rowSelection)
|
|
3405
|
+
return;
|
|
3406
|
+
if (this.rowSelection === 'single') {
|
|
3407
|
+
this.selectedRows.clear();
|
|
3408
|
+
this.selectedRows.add(row);
|
|
3409
|
+
}
|
|
3410
|
+
else {
|
|
3411
|
+
if (this.selectedRows.has(row)) {
|
|
3412
|
+
this.selectedRows.delete(row);
|
|
3413
|
+
}
|
|
3414
|
+
else {
|
|
3415
|
+
if (!event.ctrlKey)
|
|
3416
|
+
this.selectedRows.clear();
|
|
3417
|
+
this.selectedRows.add(row);
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
this.selectionChange.emit([...this.selectedRows]);
|
|
3421
|
+
}
|
|
3422
|
+
toggleRowSelect(row, event) {
|
|
3423
|
+
event.stopPropagation();
|
|
3424
|
+
this.selectedRows.has(row) ? this.selectedRows.delete(row) : this.selectedRows.add(row);
|
|
3425
|
+
this.selectionChange.emit([...this.selectedRows]);
|
|
3426
|
+
}
|
|
3427
|
+
toggleSelectAll(checked) {
|
|
3428
|
+
this.selectedRows.clear();
|
|
3429
|
+
if (checked) {
|
|
3430
|
+
this.flatRows.filter(r => r._type === 'data').forEach((r) => this.selectedRows.add(r._data));
|
|
3431
|
+
}
|
|
3432
|
+
this.selectionChange.emit([...this.selectedRows]);
|
|
3433
|
+
}
|
|
3434
|
+
get allSelected() {
|
|
3435
|
+
const dr = this.flatRows.filter(r => r._type === 'data');
|
|
3436
|
+
return dr.length > 0 && dr.every((r) => this.selectedRows.has(r._data));
|
|
3437
|
+
}
|
|
3438
|
+
get someSelected() {
|
|
3439
|
+
return this.selectedRows.size > 0 && !this.allSelected;
|
|
3440
|
+
}
|
|
3441
|
+
// ─── Pagination ───────────────────────────────────────────────────────────────
|
|
3442
|
+
get _total() { return this.autoMode ? this._filteredTotal : this.totalRecords; }
|
|
3443
|
+
get totalPages() { return Math.ceil(this._total / this.pageSize) || 1; }
|
|
3444
|
+
get pageNumbers() {
|
|
3445
|
+
const total = this.totalPages;
|
|
3446
|
+
if (total <= 7)
|
|
3447
|
+
return Array.from({ length: total }, (_, i) => i + 1);
|
|
3448
|
+
const pages = [1];
|
|
3449
|
+
if (this.currentPage > 3)
|
|
3450
|
+
pages.push(-1);
|
|
3451
|
+
for (let i = Math.max(2, this.currentPage - 1); i <= Math.min(total - 1, this.currentPage + 1); i++)
|
|
3452
|
+
pages.push(i);
|
|
3453
|
+
if (this.currentPage < total - 2)
|
|
3454
|
+
pages.push(-1);
|
|
3455
|
+
pages.push(total);
|
|
3456
|
+
return pages;
|
|
3457
|
+
}
|
|
3458
|
+
get startRecord() { return this._total === 0 ? 0 : (this.currentPage - 1) * this.pageSize + 1; }
|
|
3459
|
+
get endRecord() { return Math.min(this.currentPage * this.pageSize, this._total); }
|
|
3460
|
+
goToPage(page) {
|
|
3461
|
+
if (page < 1 || page > this.totalPages)
|
|
3462
|
+
return;
|
|
3463
|
+
this.currentPage = page;
|
|
3464
|
+
this.processData();
|
|
3465
|
+
this.pageChange.emit({ page: this.currentPage, pageSize: this.pageSize });
|
|
3466
|
+
}
|
|
3467
|
+
onPageSizeChange(size) {
|
|
3468
|
+
this.pageSize = Number(size);
|
|
3469
|
+
this.currentPage = 1;
|
|
3470
|
+
this.processData();
|
|
3471
|
+
this.pageSizeChange.emit({ page: 1, pageSize: this.pageSize });
|
|
3472
|
+
}
|
|
3473
|
+
// ─── Export CSV ───────────────────────────────────────────────────────────────
|
|
3474
|
+
exportCsv() {
|
|
3475
|
+
const cols = this.visibleCols;
|
|
3476
|
+
const header = cols.map(c => `"${c.label}"`).join(',');
|
|
3477
|
+
const filteredRows = this.getFilteredRows();
|
|
3478
|
+
const rows = filteredRows.map(row => cols.map(col => `"${String(this.getCellDisplay(row, col)).replace(/"/g, '""')}"`).join(','));
|
|
3479
|
+
const blob = new Blob([[header, ...rows].join('\n')], { type: 'text/csv;charset=utf-8;' });
|
|
3480
|
+
const a = Object.assign(document.createElement('a'), {
|
|
3481
|
+
href: URL.createObjectURL(blob), download: `${this.title || 'report'}.csv`,
|
|
3482
|
+
});
|
|
3483
|
+
a.click();
|
|
3484
|
+
URL.revokeObjectURL(a.href);
|
|
3485
|
+
}
|
|
3486
|
+
// ─── Aggregates ───────────────────────────────────────────────────────────────
|
|
3487
|
+
getAggregate(col) {
|
|
3488
|
+
if (!col.aggregate)
|
|
3489
|
+
return '';
|
|
3490
|
+
const vals = this.flatRows
|
|
3491
|
+
.filter(r => r._type === 'data')
|
|
3492
|
+
.map((r) => Number(r._data[col.key]))
|
|
3493
|
+
.filter(v => !isNaN(v));
|
|
3494
|
+
if (!vals.length)
|
|
3495
|
+
return '--';
|
|
3496
|
+
const fmt = (n) => this.decimalPipe.transform(n, '1.0-2') ?? String(n);
|
|
3497
|
+
switch (col.aggregate) {
|
|
3498
|
+
case 'sum': return fmt(vals.reduce((a, b) => a + b, 0));
|
|
3499
|
+
case 'avg': return fmt(vals.reduce((a, b) => a + b, 0) / vals.length);
|
|
3500
|
+
case 'count': return String(vals.length);
|
|
3501
|
+
case 'min': return fmt(Math.min(...vals));
|
|
3502
|
+
case 'max': return fmt(Math.max(...vals));
|
|
3503
|
+
default: return '';
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
hasAnyAggregate() { return this.visibleCols.some(c => !!c.aggregate); }
|
|
3507
|
+
// ─── Cell display ─────────────────────────────────────────────────────────────
|
|
3508
|
+
getCellDisplay(row, col) {
|
|
3509
|
+
return this.getCellDisplayByKey(row[col.key], col, row);
|
|
3510
|
+
}
|
|
3511
|
+
getCellDisplayByKey(raw, col, row) {
|
|
3512
|
+
if (col.displayFn && row !== undefined)
|
|
3513
|
+
return col.displayFn(raw, row);
|
|
3514
|
+
if (col.enums?.length)
|
|
3515
|
+
return col.enums.find(e => e.value === raw)?.label ?? (raw ?? '--');
|
|
3516
|
+
if (raw === null || raw === undefined || raw === '')
|
|
3517
|
+
return '--';
|
|
3518
|
+
switch (col.displayType) {
|
|
3519
|
+
case 'date': return this.datePipe.transform(raw, 'MM/dd/yyyy') ?? '--';
|
|
3520
|
+
case 'datetime': return this.datePipe.transform(raw, 'MM/dd/yyyy HH:mm') ?? '--';
|
|
3521
|
+
case 'time': return this.datePipe.transform(raw, 'HH:mm') ?? '--';
|
|
3522
|
+
case 'currency': return '$' + Number(raw).toLocaleString('en-US', { minimumFractionDigits: 2 });
|
|
3523
|
+
case 'number': return Number(raw).toLocaleString();
|
|
3524
|
+
case 'percentage': return Number(raw).toFixed(2) + '%';
|
|
3525
|
+
default: return String(raw);
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
getCellClass(row, col) {
|
|
3529
|
+
if (!col.cellClass)
|
|
3530
|
+
return '';
|
|
3531
|
+
return typeof col.cellClass === 'function' ? col.cellClass(row[col.key], row) : col.cellClass;
|
|
3532
|
+
}
|
|
3533
|
+
// ─── Copy cell ────────────────────────────────────────────────────────────────
|
|
3534
|
+
copyCell(value, event) {
|
|
3535
|
+
event.stopPropagation();
|
|
3536
|
+
navigator.clipboard?.writeText(value).then(() => {
|
|
3537
|
+
this.copiedCell = value;
|
|
3538
|
+
setTimeout(() => (this.copiedCell = null), 1500);
|
|
3539
|
+
});
|
|
3540
|
+
}
|
|
3541
|
+
// ─── Skeleton / empty ─────────────────────────────────────────────────────────
|
|
3542
|
+
get skeletonRows() { return Array.from({ length: Math.min(this.pageSize, 15) }); }
|
|
3543
|
+
get isEmpty() {
|
|
3544
|
+
return !this.loading && this.flatRows.length === 0;
|
|
3545
|
+
}
|
|
3546
|
+
// ─── Global click close ───────────────────────────────────────────────────────
|
|
3547
|
+
onDocumentClick() {
|
|
3548
|
+
this.openFilterKey = null;
|
|
3549
|
+
this.columnMenuKey = null;
|
|
3550
|
+
}
|
|
3551
|
+
// ─── TrackBy ─────────────────────────────────────────────────────────────────
|
|
3552
|
+
trackByRow = (_, row) => row._type === 'data' ? `d-${row._rowIndex}` : `g-${row._path}`;
|
|
3553
|
+
trackByCol = (_, col) => col.key;
|
|
3554
|
+
trackByStr = (_, s) => s;
|
|
3555
|
+
asGroupRow = (row) => row;
|
|
3556
|
+
asDataRow = (row) => row;
|
|
3557
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslReportGrid, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3558
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslReportGrid, isStandalone: false, selector: "osl-report-grid", inputs: { columns: "columns", datasource: "datasource", loading: "loading", totalRecords: "totalRecords", autoMode: "autoMode", isPaginated: "isPaginated", pageSize: "pageSize", tableHeight: "tableHeight", striped: "striped", exportable: "exportable", rowHeight: "rowHeight", rowSelection: "rowSelection", showAggregates: "showAggregates", title: "title" }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", rowClick: "rowClick", selectionChange: "selectionChange" }, host: { listeners: { "document:mousemove": "onMouseMove($event)", "document:mouseup": "onMouseUp()", "document:click": "onDocumentClick()" } }, providers: [DatePipe, DecimalPipe], viewQueries: [{ propertyName: "headerScrollRef", first: true, predicate: ["headerScrollRef"], descendants: true }, { propertyName: "bodyScrollRef", first: true, predicate: ["bodyScrollRef"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"osl-rg\" (click)=\"$event.stopPropagation()\">\n\n <!-- \u2550\u2550 TOOLBAR \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-toolbar\">\n <div class=\"osl-rg-toolbar__left\">\n @if (title) { <span class=\"osl-rg-title\">{{ title }}</span> }\n @if (!loading) {\n @if (_filteredTotal !== datasource.length) {\n <span class=\"osl-rg-filter-badge\">\n <span class=\"osl-rg-filter-badge__dot\"></span>\n {{ _filteredTotal | number }} of {{ datasource.length | number }} rows\n </span>\n } @else {\n <span class=\"osl-rg-record-count\">{{ datasource.length | number }} rows</span>\n }\n }\n </div>\n <div class=\"osl-rg-toolbar__right\">\n\n <!-- Global search -->\n <div class=\"osl-rg-global-search\">\n <svg class=\"osl-rg-global-search__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>\n </svg>\n <input class=\"osl-rg-global-search__input\" placeholder=\"Search all columns\u2026\"\n [(ngModel)]=\"globalSearch\" (ngModelChange)=\"currentPage=1; processData()\" />\n @if (globalSearch) {\n <button class=\"osl-rg-global-search__clear\" (click)=\"globalSearch=''; processData()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n </div>\n\n <!-- Sort active badge -->\n @if (sortStates.size > 0) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--active\" (click)=\"clearSort()\" title=\"Clear sort\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 5H4M11 9H4M11 13H4M18 4v16M15 7l3-3 3 3M15 17l3 3 3-3\"/>\n </svg>\n <span>{{ sortStates.size }} sort{{ sortStates.size > 1 ? 's' : '' }}</span>\n <svg class=\"rg-icon rg-icon--xs\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n\n <!-- Filter active badge -->\n @if (hasAnyFilter) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--danger\" (click)=\"clearAllFilters()\" title=\"Clear all filters\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n <span>Filtered</span>\n <svg class=\"rg-icon rg-icon--xs\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n\n <!-- Group toggle -->\n <button class=\"osl-rg-toolbar-btn\" [class.osl-rg-toolbar-btn--on]=\"showGroupPanel\"\n (click)=\"showGroupPanel=!showGroupPanel\" title=\"Group panel\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"2\" y=\"7\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M16 3H8a2 2 0 0 0-2 2v2h12V5a2 2 0 0 0-2-2z\"/>\n </svg>\n <span>Group</span>\n </button>\n\n <!-- Expand / collapse when grouped -->\n @if (activeGroups.length > 0) {\n <button class=\"osl-rg-toolbar-btn\" (click)=\"expandAll()\" title=\"Expand all\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 20 5-5 5 5M7 4l5 5 5-5\"/></svg>\n </button>\n <button class=\"osl-rg-toolbar-btn\" (click)=\"collapseAll()\" title=\"Collapse all\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 15 5-5 5 5M7 9l5 5 5 5\"/></svg>\n </button>\n }\n\n <!-- Auto-size all -->\n <button class=\"osl-rg-toolbar-btn\" (click)=\"autoSizeAll()\" title=\"Auto-size all columns\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3M8 15l-5 3 5 3M16 15l5 3-5 3\"/>\n </svg>\n <span>Auto-size</span>\n </button>\n\n <!-- Column config -->\n <button class=\"osl-rg-toolbar-btn\" [class.osl-rg-toolbar-btn--on]=\"showColumnConfig\"\n (click)=\"showColumnConfig=!showColumnConfig; $event.stopPropagation()\" title=\"Column settings\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M4 6h16M4 12h16M4 18h16\"/>\n <circle cx=\"8\" cy=\"6\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"16\" cy=\"12\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"8\" cy=\"18\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n <span>Columns</span>\n </button>\n\n <!-- Export Excel -->\n @if (exportable) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--excel\" (click)=\"exportCsv()\" title=\"Export to Excel (CSV)\">\n <svg class=\"rg-icon rg-icon--excel\" viewBox=\"0 0 24 24\">\n <rect width=\"24\" height=\"24\" rx=\"3\" fill=\"#1D6F42\"/>\n <path d=\"M13.5 3H7a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7.5L13.5 3z\" fill=\"#fff\" opacity=\".15\"/>\n <path d=\"M13.5 3v4.5H18L13.5 3z\" fill=\"#fff\" opacity=\".3\"/>\n <path d=\"M8 9.5l2.3 3.5L8 16.5h1.6l1.4-2.3 1.4 2.3H14l-2.3-3.5 2.3-3.5h-1.6l-1.4 2.3-1.4-2.3H8z\" fill=\"#fff\"/>\n </svg>\n <span>Export</span>\n </button>\n }\n </div>\n </div>\n\n <!-- \u2550\u2550 GROUP PANEL \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (showGroupPanel) {\n <div class=\"osl-rg-group-panel\">\n <span class=\"osl-rg-group-panel__label\">Group by:</span>\n @if (activeGroups.length === 0) {\n <span class=\"osl-rg-group-panel__hint\">Use the \u22EE column menu \u2192 \"Group by this\"</span>\n }\n <div class=\"osl-rg-group-chips\" cdkDropList cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"activeGroups\" (cdkDropListDropped)=\"onGroupPanelDrop($event)\">\n @for (key of activeGroups; track key) {\n <div class=\"osl-rg-group-chip\" cdkDrag>\n <svg cdkDragHandle class=\"rg-drag-handle\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.5\"/><circle cx=\"15\" cy=\"5\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"15\" cy=\"12\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"19\" r=\"1.5\"/><circle cx=\"15\" cy=\"19\" r=\"1.5\"/>\n </svg>\n <span>{{ getGroupColLabel(key) }}</span>\n <button class=\"osl-rg-group-chip__remove\" (click)=\"removeGroup(key)\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n </div>\n }\n </div>\n @if (activeGroups.length > 0) {\n <button class=\"osl-rg-group-panel__clear\" (click)=\"clearGroups()\">Clear groups</button>\n }\n </div>\n }\n\n <!-- \u2550\u2550 STICKY HEADER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-header-wrap\" #headerScrollRef>\n <div class=\"osl-rg-header-row\" [style.minWidth.px]=\"totalWidth\">\n\n <!-- Select-all checkbox -->\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-th osl-rg-th--checkbox\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\"\n [checked]=\"allSelected\" [indeterminate]=\"someSelected\"\n (change)=\"toggleSelectAll($any($event.target).checked)\" />\n </div>\n }\n\n <!-- Column headers (drag-to-reorder) -->\n <div class=\"osl-rg-header-cols\" cdkDropList cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"_cols\" (cdkDropListDropped)=\"onColumnReorder($event)\"\n style=\"display:flex;flex:1;min-width:0;\">\n\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-th\"\n [class.osl-rg-th--pinned]=\"col._pinned\"\n [class.osl-rg-th--sorted]=\"getSortState(col.key)\"\n [class.osl-rg-th--filtered]=\"hasActiveFilter(col.key)\"\n [style.width.px]=\"col._width\"\n [style.minWidth.px]=\"col._width\"\n [style.left.px]=\"col._pinned ? col._stickyLeft : null\"\n [style.position]=\"col._pinned ? 'sticky' : 'relative'\"\n [style.zIndex]=\"col._pinned ? 3 : 1\"\n cdkDrag [cdkDragDisabled]=\"col._pinned\"\n (cdkDragStarted)=\"onColumnDragStarted()\"\n (cdkDragEnded)=\"onColumnDragEnded()\"\n (click)=\"onHeaderClick(col, $event)\">\n\n <div *cdkDragPlaceholder class=\"osl-rg-th-drag-placeholder\"></div>\n\n <!-- Pin badge -->\n @if (col._pinned) {\n <span class=\"osl-rg-th-pin-badge\" title=\"Pinned\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"9\" height=\"9\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n </span>\n }\n\n <!-- Label row -->\n <div class=\"osl-rg-th-content\">\n <span class=\"osl-rg-th-label\" [title]=\"col.label\">{{ col.label }}</span>\n <div class=\"osl-rg-th-actions\">\n\n <!-- Sort indicator -->\n @if (col.sortable) {\n <span class=\"osl-rg-sort-icon\"\n [class.osl-rg-sort-icon--asc]=\"getSortState(col.key)?.asc === true\"\n [class.osl-rg-sort-icon--desc]=\"getSortState(col.key)?.asc === false\">\n @if (getSortState(col.key); as s) {\n @if (s.asc) {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 19V5m-7 7 7-7 7 7\"/></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14m7-7-7 7-7-7\"/></svg>\n }\n @if (sortStates.size > 1) { <sup class=\"osl-rg-sort-badge\">{{ s.index + 1 }}</sup> }\n } @else {\n <svg class=\"osl-rg-sort-idle\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 9l4-4 4 4M16 15l-4 4-4-4\"/></svg>\n }\n </span>\n }\n\n <!-- Excel filter button -->\n @if (col.filterable) {\n <button class=\"osl-rg-filter-btn\" [class.osl-rg-filter-btn--active]=\"hasActiveFilter(col.key)\"\n (click)=\"openExcelFilter(col.key, $event)\" title=\"Filter\">\n @if (hasActiveFilter(col.key)) {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"/></svg>\n }\n </button>\n }\n\n <!-- Column menu -->\n <button class=\"osl-rg-col-menu-btn\" (click)=\"openColumnMenu(col.key, $event)\" title=\"Column options\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><circle cx=\"12\" cy=\"5\" r=\"2\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/><circle cx=\"12\" cy=\"19\" r=\"2\"/></svg>\n </button>\n\n </div>\n </div>\n\n <!-- Per-column search -->\n @if (col.searchable) {\n <div class=\"osl-rg-col-search\" (click)=\"$event.stopPropagation()\">\n <svg class=\"osl-rg-col-search__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m18 18-3.5-3.5\"/>\n </svg>\n <input class=\"osl-rg-col-search__input\" [placeholder]=\"col.label + '\u2026'\"\n [ngModel]=\"columnSearch[col.key]\" (ngModelChange)=\"onColumnSearch(col.key, $event)\" />\n @if (columnSearch[col.key]) {\n <button class=\"osl-rg-col-search__clear\" (click)=\"columnSearch[col.key]=''; processData()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n </div>\n }\n\n <!-- Resize handle -->\n @if (col.resizable) {\n <div class=\"osl-rg-resize-handle\" (mousedown)=\"startResize(col, $event)\" (dblclick)=\"autoSizeColumn(col, $event)\" (click)=\"$event.stopPropagation()\"></div>\n }\n\n\n </div><!-- /osl-rg-th -->\n }\n\n </div><!-- /header-cols -->\n </div><!-- /header-row -->\n </div><!-- /header-wrap -->\n\n <!-- \u2550\u2550 SCROLLABLE BODY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-body\" [style.height]=\"tableHeight\" #bodyScrollRef (scroll)=\"onBodyScroll($event)\">\n <div class=\"osl-rg-inner\" [style.minWidth.px]=\"totalWidth\">\n\n <!-- Skeleton loading -->\n @if (loading) {\n @for (sk of skeletonRows; track $index) {\n <div class=\"osl-rg-row osl-rg-row--skeleton\" [style.height.px]=\"rowHeight\">\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-td osl-rg-td--checkbox\"><div class=\"osl-rg-skel\" style=\"width:14px;height:14px;border-radius:3px;\"></div></div>\n }\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-td\" [style.width.px]=\"col._width\" [style.minWidth.px]=\"col._width\">\n <div class=\"osl-rg-skel\" [style.width]=\"$index % 3 === 0 ? '55%' : $index % 3 === 1 ? '80%' : '65%'\"></div>\n </div>\n }\n </div>\n }\n\n } @else if (isEmpty) {\n <!-- Empty state -->\n <div class=\"osl-rg-empty\">\n <div class=\"osl-rg-empty__icon\">\n <svg viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#d1d5db\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"6\" y=\"6\" width=\"36\" height=\"36\" rx=\"4\"/>\n <path d=\"M6 18h36M6 30h36M18 6v36\"/>\n </svg>\n </div>\n <p class=\"osl-rg-empty__title\">No records found</p>\n <p class=\"osl-rg-empty__sub\">\n @if (hasAnyFilter) { Try adjusting your filters or search terms }\n @else { No data to display }\n </p>\n @if (hasAnyFilter) {\n <button class=\"osl-rg-empty__action\" (click)=\"clearAllFilters()\">Clear all filters</button>\n }\n </div>\n\n } @else {\n <!-- Data rows -->\n @for (row of flatRows; track trackByRow($index, row)) {\n\n @if (row._type === 'group') {\n <div class=\"osl-rg-group-row\"\n [class.osl-rg-group-row--l0]=\"asGroupRow(row)._level === 0\"\n [class.osl-rg-group-row--l1]=\"asGroupRow(row)._level === 1\"\n [class.osl-rg-group-row--l2]=\"asGroupRow(row)._level >= 2\"\n [style.height.px]=\"rowHeight\"\n [style.paddingLeft.px]=\"asGroupRow(row)._level * 20 + 12\"\n (click)=\"toggleGroup(asGroupRow(row)._path)\">\n @if (asGroupRow(row)._expanded) {\n <svg class=\"osl-rg-group-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>\n } @else {\n <svg class=\"osl-rg-group-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 18 6-6-6-6\"/></svg>\n }\n <span class=\"osl-rg-group-row__key\">{{ asGroupRow(row)._colLabel }}:</span>\n <span class=\"osl-rg-group-row__value\">{{ asGroupRow(row)._label }}</span>\n <span class=\"osl-rg-group-row__count\">{{ asGroupRow(row)._count | number }} rows</span>\n </div>\n\n } @else {\n <div class=\"osl-rg-row\"\n [class.osl-rg-row--striped]=\"striped && asDataRow(row)._rowIndex % 2 === 1\"\n [class.osl-rg-row--selected]=\"selectedRows.has(asDataRow(row)._data)\"\n [style.height.px]=\"rowHeight\"\n (click)=\"onRowClick(asDataRow(row)._data, $event)\">\n\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-td osl-rg-td--checkbox\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\"\n [checked]=\"selectedRows.has(asDataRow(row)._data)\"\n (change)=\"toggleRowSelect(asDataRow(row)._data, $event)\" />\n </div>\n }\n\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-td\"\n [class.osl-rg-td--pinned]=\"col._pinned\"\n [ngClass]=\"getCellClass(asDataRow(row)._data, col)\"\n [style.width.px]=\"col._width\"\n [style.minWidth.px]=\"col._width\"\n [style.left.px]=\"col._pinned ? col._stickyLeft : null\"\n [style.position]=\"col._pinned ? 'sticky' : 'relative'\"\n [style.zIndex]=\"col._pinned ? 2 : 0\"\n [style.textAlign]=\"col.align ?? 'left'\"\n (dblclick)=\"copyCell(getCellDisplay(asDataRow(row)._data, col), $event)\"\n [title]=\"getCellDisplay(asDataRow(row)._data, col)\">\n <span class=\"osl-rg-cell-text\">{{ getCellDisplay(asDataRow(row)._data, col) }}</span>\n </div>\n }\n\n </div>\n }\n\n }\n }\n\n </div><!-- /osl-rg-inner -->\n </div><!-- /osl-rg-body -->\n\n <!-- \u2550\u2550 AGGREGATE FOOTER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (showAggregates && hasAnyAggregate() && !loading) {\n <div class=\"osl-rg-aggregate-row\" [style.minWidth.px]=\"totalWidth\">\n @if (rowSelection === 'multiple') { <div class=\"osl-rg-agg-cell osl-rg-agg-cell--checkbox\"></div> }\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-agg-cell\" [style.width.px]=\"col._width\" [style.minWidth.px]=\"col._width\" [style.textAlign]=\"col.align ?? 'left'\">\n @if (col.aggregate) {\n <span class=\"osl-rg-agg-label\">{{ col.aggregate | uppercase }}</span>\n <span class=\"osl-rg-agg-value\">{{ getAggregate(col) }}</span>\n }\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550 PAGINATION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (isPaginated) {\n <div class=\"osl-rg-pagination\">\n <span class=\"osl-rg-pagination__info\">\n @if (loading) { Loading\u2026 }\n @else if (_total > 0) { Showing <strong>{{ startRecord | number }}\u2013{{ endRecord | number }}</strong> of <strong>{{ _total | number }}</strong> records }\n @else { No records }\n </span>\n <div class=\"osl-rg-pagination__controls\">\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage===1||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/></svg>\n </button>\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(currentPage-1)\" [disabled]=\"currentPage===1||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m15 18-6-6 6-6\"/></svg>\n Prev\n </button>\n @for (page of pageNumbers; track $index) {\n @if (page === -1) { <span class=\"osl-rg-page-ellipsis\">\u2026</span> }\n @else {\n <button class=\"osl-rg-page-btn osl-rg-page-btn--num\" [class.osl-rg-page-btn--active]=\"page===currentPage\"\n [disabled]=\"loading\" (click)=\"goToPage(page)\">{{ page }}</button>\n }\n }\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(currentPage+1)\" [disabled]=\"currentPage===totalPages||loading\">\n Next\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m9 18 6-6-6-6\"/></svg>\n </button>\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage===totalPages||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m13 17 5-5-5-5M6 17l5-5-5-5\"/></svg>\n </button>\n </div>\n <div class=\"osl-rg-pagination__size\">\n <span class=\"osl-rg-pagination__size-label\">Rows per page</span>\n <select class=\"osl-rg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\n @for (opt of pageSizeOptions; track opt) { <option [value]=\"opt\">{{ opt }}</option> }\n </select>\n </div>\n </div>\n }\n\n</div><!-- /osl-rg -->\n\n\n<!-- \u2550\u2550 EXCEL FILTER DROPDOWN (fixed, outside overflow containers) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (openFilterKey && excelFilterState[openFilterKey]) {\n <div class=\"osl-rg-excel-filter\"\n [style.top.px]=\"filterDropdownPos.top\"\n [style.left.px]=\"filterDropdownPos.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-excel-filter__header\">\n <div class=\"osl-rg-excel-filter__title\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"12\" height=\"12\" style=\"flex-shrink:0;\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n Filter: {{ getColumnByKey(openFilterKey)?.label }}\n </div>\n @if (hasActiveFilter(openFilterKey)) {\n <button class=\"osl-rg-excel-filter__clear-btn\" (click)=\"clearExcelFilter(openFilterKey!)\">Clear</button>\n }\n </div>\n <div class=\"osl-rg-excel-filter__search\">\n <input class=\"osl-rg-excel-filter__search-input\" placeholder=\"Search values\u2026\"\n [(ngModel)]=\"excelFilterState[openFilterKey].search\" />\n </div>\n <div class=\"osl-rg-excel-filter__select-all\">\n <label class=\"osl-rg-excel-filter__item osl-rg-excel-filter__item--all\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [checked]=\"isAllExcelChecked(openFilterKey)\"\n (change)=\"toggleAllExcelFilter(openFilterKey!, $any($event.target).checked)\" />\n <span class=\"osl-rg-excel-filter__item-label\">(Select All)</span>\n </label>\n </div>\n <div class=\"osl-rg-excel-filter__list\">\n @for (item of getFilteredExcelItems(openFilterKey); track item.value) {\n <label class=\"osl-rg-excel-filter__item\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [(ngModel)]=\"item.checked\" />\n <span class=\"osl-rg-excel-filter__item-label\">{{ item.label }}</span>\n </label>\n }\n </div>\n <div class=\"osl-rg-excel-filter__footer\">\n <button class=\"osl-rg-excel-filter__apply\" (click)=\"applyExcelFilter(openFilterKey!)\">Apply Filter</button>\n <button class=\"osl-rg-excel-filter__cancel\" (click)=\"openFilterKey=null\">Cancel</button>\n </div>\n </div>\n}\n\n\n<!-- \u2550\u2550 COLUMN CONTEXT MENU (fixed) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (columnMenuKey && getColumnByKey(columnMenuKey); as menuCol) {\n <div class=\"osl-rg-col-menu\"\n [style.top.px]=\"columnMenuPos.top\" [style.left.px]=\"columnMenuPos.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-col-menu__header\">{{ menuCol.label }}</div>\n @if (menuCol.sortable) {\n <button class=\"osl-rg-col-menu__item\"\n (click)=\"sortStates.clear(); sortStates.set(menuCol.key,{key:menuCol.key,asc:true,index:0}); sortCounter=1; processData(); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 19V5m-7 7 7-7 7 7\"/></svg>\n Sort A \u2192 Z\n </button>\n <button class=\"osl-rg-col-menu__item\"\n (click)=\"sortStates.clear(); sortStates.set(menuCol.key,{key:menuCol.key,asc:false,index:0}); sortCounter=1; processData(); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14m7-7-7 7-7-7\"/></svg>\n Sort Z \u2192 A\n </button>\n }\n @if (menuCol.groupable) {\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item\" (click)=\"addGroup(menuCol.key); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"7\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M16 3H8a2 2 0 0 0-2 2v2h12V5a2 2 0 0 0-2-2z\"/></svg>\n Group by this\n </button>\n }\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item\" (click)=\"toggleColumnPin(menuCol)\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n {{ menuCol._pinned ? 'Unpin column' : 'Pin to left' }}\n </button>\n <button class=\"osl-rg-col-menu__item\" (click)=\"autoSizeColumn(menuCol); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3M8 15l-5 3 5 3M16 15l5 3-5 3\"/></svg>\n Auto-size column\n </button>\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item osl-rg-col-menu__item--danger\" (click)=\"toggleColumnVisibility(menuCol); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24M1 1l22 22\"/>\n </svg>\n Hide column\n </button>\n </div>\n}\n\n\n<!-- \u2550\u2550 COLUMN CONFIG SIDE DRAWER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (showColumnConfig) {\n <div class=\"osl-rg-config-backdrop\" (click)=\"showColumnConfig=false\"></div>\n <div class=\"osl-rg-config-panel\" (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-config-panel__header\">\n <div class=\"osl-rg-config-panel__title\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"16\" height=\"16\">\n <path d=\"M4 6h16M4 12h16M4 18h16\"/>\n <circle cx=\"8\" cy=\"6\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"16\" cy=\"12\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"8\" cy=\"18\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n Column Settings\n </div>\n <button class=\"osl-rg-config-panel__close\" (click)=\"showColumnConfig=false\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n </div>\n <div class=\"osl-rg-config-panel__actions\">\n <button class=\"osl-rg-config-action\" (click)=\"autoSizeAll()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3\"/></svg>\n Auto-size all\n </button>\n <button class=\"osl-rg-config-action\" (click)=\"showAllColumns()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>\n Show all\n </button>\n </div>\n <div class=\"osl-rg-config-hint\">Drag to reorder \u00B7 Toggle to show/hide \u00B7 Pin to freeze</div>\n <div class=\"osl-rg-config-list\" cdkDropList (cdkDropListDropped)=\"onConfigColumnReorder($event)\">\n @for (col of _cols; track col.key) {\n <div class=\"osl-rg-config-item\" cdkDrag [class.osl-rg-config-item--hidden]=\"col._hidden\">\n <svg cdkDragHandle class=\"rg-drag-handle\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.5\"/><circle cx=\"15\" cy=\"5\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"15\" cy=\"12\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"19\" r=\"1.5\"/><circle cx=\"15\" cy=\"19\" r=\"1.5\"/>\n </svg>\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [checked]=\"!col._hidden\" (change)=\"toggleColumnVisibility(col)\" />\n <span class=\"osl-rg-config-item__label\" [class.osl-rg-config-item__label--hidden]=\"col._hidden\">{{ col.label }}</span>\n <button class=\"osl-rg-config-item__pin-btn\" (click)=\"toggleColumnPin(col)\"\n [class.osl-rg-config-item__pin-btn--active]=\"col._pinned\"\n [title]=\"col._pinned ? 'Unpin' : 'Pin to left'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"12\" height=\"12\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n </button>\n </div>\n }\n </div>\n </div>\n}\n\n\n<!-- \u2550\u2550 COPY TOAST \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (copiedCell !== null) {\n <div class=\"osl-rg-copy-toast\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Copied to clipboard\n </div>\n}\n", styles: ["@keyframes rg-pulse{0%,to{opacity:1}50%{opacity:.4}}@keyframes rg-fade-up{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes rg-slide-in{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.rg-icon{width:15px;height:15px;flex-shrink:0;display:block}.rg-icon--sm{width:14px;height:14px}.rg-icon--xs{width:12px;height:12px}.rg-icon--excel{width:18px;height:18px}.rg-drag-handle{width:14px;height:14px;flex-shrink:0;color:#d1d5db;cursor:grab;display:block}.rg-drag-handle:active{cursor:grabbing}.osl-rg{position:relative;display:flex;flex-direction:column;width:100%;font-family:inherit;font-size:13px;color:#111827;background:#fff;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:10px;overflow:hidden;box-shadow:0 2px 8px #00000012,0 1px 2px #0000000a;-webkit-user-select:none;user-select:none}.osl-rg-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 14px;background:linear-gradient(135deg,#f8faff,#f3f4f6);border-bottom:1px solid var(--osl-border-color, #e5e7eb);flex-shrink:0;flex-wrap:wrap}.osl-rg-toolbar__left{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.osl-rg-toolbar__right{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.osl-rg-title{font-size:14px;font-weight:700;color:#111827;letter-spacing:-.01em}.osl-rg-record-count{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:#9ca3af;font-weight:500}.osl-rg-record-count svg{color:#d1d5db}.osl-rg-filter-badge{display:inline-flex;align-items:center;gap:5px;padding:2px 9px 2px 6px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:12px;font-size:11px;color:#1d4ed8;font-weight:600}.osl-rg-filter-badge__dot{width:6px;height:6px;border-radius:50%;background:#2563eb;animation:rg-pulse 1.5s ease-in-out infinite;flex-shrink:0}.osl-rg-toolbar-btn{display:inline-flex;align-items:center;gap:5px;height:32px;padding:0 11px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:7px;background:#fff;color:#5a6478;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;transition:all .15s ease}.osl-rg-toolbar-btn svg{flex-shrink:0}.osl-rg-toolbar-btn:hover{background:#f3f4f6;color:#1f2937;border-color:#c4c9d4;box-shadow:0 1px 3px #0000000f}.osl-rg-toolbar-btn--on{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8}.osl-rg-toolbar-btn--active{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8;font-weight:600}.osl-rg-toolbar-btn--danger{background:#fff5f5;border-color:#fca5a5;color:#dc2626}.osl-rg-toolbar-btn--danger:hover{background:#fee2e2;border-color:#f87171}.osl-rg-toolbar-btn--excel{background:linear-gradient(135deg,#1a7a3e,#1d6f42);border-color:#166534;color:#fff;font-weight:600;box-shadow:0 1px 3px #1665344d}.osl-rg-toolbar-btn--excel:hover{background:linear-gradient(135deg,#166534,#145a2c);border-color:#14532d;color:#fff;box-shadow:0 2px 6px #16653466}.osl-rg-global-search{position:relative;display:inline-flex;align-items:center}.osl-rg-global-search__icon{position:absolute;left:9px;width:14px;height:14px;color:#9ca3af;pointer-events:none;display:block}.osl-rg-global-search__input{height:32px;padding:0 30px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:7px;background:#fff;font-size:12.5px;font-family:inherit;color:#111827;outline:none;width:210px;transition:border-color .15s,width .2s,box-shadow .15s}.osl-rg-global-search__input::placeholder{color:#b0b7c3}.osl-rg-global-search__input:focus{border-color:var(--osl-primary, #2563eb);width:260px;box-shadow:0 0 0 3px #2563eb1a}.osl-rg-global-search__clear{position:absolute;right:6px;display:flex;align-items:center;justify-content:center;width:18px;height:18px;border:none;background:transparent;color:#9ca3af;cursor:pointer;padding:0;border-radius:4px;transition:all .12s}.osl-rg-global-search__clear svg{width:12px;height:12px}.osl-rg-global-search__clear:hover{color:#374151;background:#0000000d}.osl-rg-group-panel{display:flex;align-items:center;gap:8px;padding:6px 14px;background:linear-gradient(135deg,#fafbff,#f5f7ff);border-bottom:1px dashed #dde5ff;min-height:40px;flex-shrink:0;flex-wrap:wrap}.osl-rg-group-panel__label{display:flex;align-items:center;gap:5px;font-size:11px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;flex-shrink:0}.osl-rg-group-panel__label svg{color:#9ca3af}.osl-rg-group-panel__hint{font-size:11px;color:#b0b7c3;font-style:italic}.osl-rg-group-panel__clear{margin-left:auto;font-size:11px;color:#6b7280;background:none;border:none;cursor:pointer;text-decoration:underline;flex-shrink:0;padding:0;font-family:inherit}.osl-rg-group-panel__clear:hover{color:#dc2626}.osl-rg-group-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center;flex:1;min-height:26px}.osl-rg-group-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 6px 3px 5px;background:linear-gradient(135deg,#dbeafe,#eff6ff);border:1px solid #bfdbfe;border-radius:20px;font-size:12px;font-weight:600;color:#1e40af;cursor:grab;-webkit-user-select:none;user-select:none;box-shadow:0 1px 2px #2563eb1a;transition:box-shadow .1s}.osl-rg-group-chip:active{cursor:grabbing;box-shadow:0 2px 6px #2563eb33}.osl-rg-group-chip .rg-drag-handle{color:#93c5fd}.osl-rg-group-chip__remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border:none;background:transparent;cursor:pointer;padding:0;border-radius:50%;transition:all .1s}.osl-rg-group-chip__remove svg{width:10px;height:10px;color:#93c5fd}.osl-rg-group-chip__remove:hover{background:#dbeafe}.osl-rg-group-chip__remove:hover svg{color:#1e40af}.osl-rg-table-area{display:flex;flex-direction:column;flex:1 1 0;overflow:hidden;position:relative;min-height:0}.osl-rg-header-wrap{overflow:hidden;flex-shrink:0;border-bottom:2px solid #dde5ff;background:linear-gradient(180deg,#f0f4ff,#e8efff);z-index:10;position:relative}.osl-rg-header-row{display:flex;align-items:stretch}.osl-rg-header-cols{display:flex}.osl-rg-th{position:relative;display:flex;flex-direction:column;background:transparent;border-right:1px solid #dde5ff;cursor:pointer;flex-shrink:0;transition:background .12s;overflow:visible}.osl-rg-th:last-child{border-right:none}.osl-rg-th:hover{background:#2563eb0f}.osl-rg-th--pinned{background:linear-gradient(180deg,#e8efff,#dde8ff);border-right:1px solid #bfdbfe;z-index:3}.osl-rg-th--pinned:after{content:\"\";position:absolute;right:-5px;top:0;bottom:0;width:5px;background:linear-gradient(to right,rgba(37,99,235,.14),transparent);pointer-events:none;z-index:4}.osl-rg-th--sorted{background:#6366f114}.osl-rg-th--filtered{background:#f59e0b12}.osl-rg-th-drag-placeholder{background:#e0e7ff;border:2px dashed #6366f1;border-radius:4px;opacity:.6}.osl-rg-th-pin-badge{position:absolute;top:3px;right:24px;color:var(--osl-primary, #2563eb);opacity:.6;display:flex;align-items:center}.osl-rg-th-content{display:flex;align-items:center;justify-content:space-between;gap:4px;padding:0 8px 0 10px;height:36px;min-height:36px}.osl-rg-th-label{font-size:11.5px;font-weight:700;color:#1e3a6e;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.osl-rg-th-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.osl-rg-sort-icon{display:inline-flex;align-items:center;position:relative;color:#9ca3af;transition:color .12s;width:14px;height:14px}.osl-rg-sort-icon svg{width:13px;height:13px;display:block}.osl-rg-sort-icon--asc,.osl-rg-sort-icon--desc{color:var(--osl-primary, #2563eb)}.osl-rg-sort-idle{opacity:.3}.osl-rg-sort-badge{position:absolute;top:-4px;right:-6px;font-size:8px;font-weight:700;background:var(--osl-primary, #2563eb);color:#fff;border-radius:50%;width:11px;height:11px;display:flex;align-items:center;justify-content:center;line-height:1}.osl-rg-filter-btn,.osl-rg-col-menu-btn{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;background:transparent;color:#b0b7c3;cursor:pointer;padding:0;border-radius:5px;transition:all .12s}.osl-rg-filter-btn svg,.osl-rg-col-menu-btn svg{width:13px;height:13px;display:block}.osl-rg-filter-btn:hover,.osl-rg-col-menu-btn:hover{background:#00000012;color:#374151}.osl-rg-filter-btn--active{color:var(--osl-primary, #2563eb)!important;background:#2563eb1f!important}.osl-rg-col-search{position:relative;display:flex;align-items:center;padding:3px 6px;border-top:1px solid #e0e7ff;background:#ffffff80}.osl-rg-col-search__icon{position:absolute;left:10px;width:12px;height:12px;display:block;pointer-events:none;color:#c4cad6}.osl-rg-col-search__input{width:100%;height:23px;padding:0 18px 0 22px;border:1px solid #dde5ff;border-radius:5px;font-size:11px;font-family:inherit;background:#fff;color:#111827;outline:none;transition:border-color .12s,box-shadow .12s}.osl-rg-col-search__input::placeholder{color:#c4cad6}.osl-rg-col-search__input:focus{border-color:var(--osl-primary, #2563eb);box-shadow:0 0 0 2px #2563eb1a}.osl-rg-col-search__clear{position:absolute;right:8px;display:flex;align-items:center;width:14px;height:14px;border:none;background:transparent;color:#9ca3af;cursor:pointer;padding:0;border-radius:3px;transition:all .1s}.osl-rg-col-search__clear svg{width:10px;height:10px;display:block}.osl-rg-col-search__clear:hover{color:#374151;background:#0000000f}.osl-rg-resize-handle{position:absolute;right:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:5}.osl-rg-resize-handle:after{content:\"\";position:absolute;right:2px;top:15%;bottom:15%;width:2px;border-radius:2px;background:#a5b4fc;opacity:0;transition:opacity .15s}.osl-rg-resize-handle:hover:after{opacity:1}.osl-rg-th:hover .osl-rg-resize-handle:after{opacity:.4}.osl-rg-excel-filter{position:fixed;z-index:9999;width:250px;background:#fff;border:1px solid #e5e7eb;border-radius:10px;box-shadow:0 12px 32px #00000026,0 2px 8px #00000012;animation:rg-fade-up .14s ease;overflow:hidden}.osl-rg-excel-filter__header{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;background:linear-gradient(135deg,#f0f4ff,#e8efff);border-bottom:1px solid #dde5ff}.osl-rg-excel-filter__title{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:#1e3a6e;text-transform:uppercase;letter-spacing:.06em}.osl-rg-excel-filter__clear-btn{font-size:11px;color:#dc2626;background:none;border:none;cursor:pointer;font-weight:600;padding:2px 6px;border-radius:4px;font-family:inherit;transition:background .1s}.osl-rg-excel-filter__clear-btn:hover{background:#fee2e2}.osl-rg-excel-filter__search{padding:7px 8px;border-bottom:1px solid #f3f4f6;position:relative}.osl-rg-excel-filter__search-input{width:100%;height:28px;padding:0 8px 0 28px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .12s;color:#111827}.osl-rg-excel-filter__search-input::placeholder{color:#b0b7c3}.osl-rg-excel-filter__search-input:focus{border-color:var(--osl-primary, #2563eb)}.osl-rg-excel-filter__select-all{padding:0 8px;border-bottom:2px solid #f3f4f6;background:#fafbff}.osl-rg-excel-filter__list{max-height:210px;overflow-y:auto;padding:3px 8px}.osl-rg-excel-filter__list::-webkit-scrollbar{width:5px}.osl-rg-excel-filter__list::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:4px}.osl-rg-excel-filter__list::-webkit-scrollbar-thumb:hover{background:#9ca3af}.osl-rg-excel-filter__item{display:flex;align-items:center;gap:8px;padding:5px 4px;font-size:12.5px;color:#374151;cursor:pointer;border-radius:5px;transition:background .1s}.osl-rg-excel-filter__item:hover{background:#f3f4f6}.osl-rg-excel-filter__item--all{font-weight:600;color:#1f2937;padding:6px 4px}.osl-rg-excel-filter__item-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.osl-rg-excel-filter__footer{display:flex;gap:6px;padding:8px 10px;border-top:1px solid #f3f4f6;background:#f9fafb}.osl-rg-excel-filter__apply{flex:1;height:30px;border:none;border-radius:6px;font-size:12px;font-family:inherit;font-weight:600;cursor:pointer;background:var(--osl-primary, #2563eb);color:#fff;transition:background .12s}.osl-rg-excel-filter__apply:hover{background:#1d4ed8}.osl-rg-excel-filter__cancel{height:30px;padding:0 14px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;background:#fff;color:#6b7280;transition:all .12s}.osl-rg-excel-filter__cancel:hover{background:#f3f4f6;border-color:#9ca3af}.osl-rg-th--checkbox,.osl-rg-td--checkbox{display:flex;align-items:center;justify-content:center;width:36px;min-width:36px;flex-shrink:0;border-right:1px solid #dde5ff}.osl-rg-checkbox{width:14px;height:14px;cursor:pointer;accent-color:var(--osl-primary, #2563eb);flex-shrink:0}.osl-rg-body{overflow-x:auto;overflow-y:auto;flex-shrink:0}.osl-rg-body::-webkit-scrollbar{width:8px;height:8px}.osl-rg-body::-webkit-scrollbar-track{background:#f9fafb}.osl-rg-body::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:4px}.osl-rg-body::-webkit-scrollbar-thumb:hover{background:#9ca3af}.osl-rg-body::-webkit-scrollbar-corner{background:#f9fafb}.osl-rg-inner{display:block}.osl-rg-row{display:flex;align-items:stretch;border-bottom:1px solid #f3f4f6;transition:background .08s;cursor:default}.osl-rg-row:hover{background:#f0f5ff!important}.osl-rg-row--striped{background:#fafbff}.osl-rg-row--selected{background:#eff6ff!important;border-bottom-color:#dbeafe}.osl-rg-row--skeleton{pointer-events:none}.osl-rg-td{display:flex;align-items:center;padding:0 12px;border-right:1px solid #f0f2f7;overflow:hidden;background:inherit;flex-shrink:0}.osl-rg-td:last-child{border-right:none}.osl-rg-td--pinned{background:#fff;border-right:1px solid #e0e7ff;z-index:2}.osl-rg-td--pinned:after{content:\"\";position:absolute;right:-5px;top:0;bottom:0;width:5px;background:linear-gradient(to right,rgba(37,99,235,.07),transparent);pointer-events:none}.osl-rg-cell-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;width:100%;font-size:13px;color:#1f2937;line-height:1.4}.rg-profit-pos .osl-rg-cell-text{color:#15803d;font-weight:600}.rg-profit-neg .osl-rg-cell-text{color:#dc2626;font-weight:600}.rg-status-delivered .osl-rg-cell-text{color:#15803d;font-weight:600}.rg-status-shipped .osl-rg-cell-text{color:#2563eb;font-weight:600}.rg-status-processing .osl-rg-cell-text{color:#d97706;font-weight:600}.rg-status-pending .osl-rg-cell-text{color:#6b7280;font-weight:500}.rg-status-cancelled .osl-rg-cell-text{color:#dc2626;font-weight:500;text-decoration:line-through;opacity:.75}.osl-rg-group-row{display:flex;align-items:center;gap:7px;border-bottom:1px solid #e5e7eb;font-weight:600;cursor:pointer;flex-shrink:0;transition:filter .1s}.osl-rg-group-row--l0{background:linear-gradient(135deg,#eff6ff,#dbeafe);color:#1e40af;border-left:4px solid #3b82f6}.osl-rg-group-row--l1{background:linear-gradient(135deg,#f5f3ff,#ede9fe);color:#5b21b6;border-left:4px solid #8b5cf6}.osl-rg-group-row--l2{background:linear-gradient(135deg,#fff7ed,#fef3c7);color:#92400e;border-left:4px solid #f59e0b}.osl-rg-group-row:hover{filter:brightness(.97)}.osl-rg-group-row__key{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;opacity:.65;flex-shrink:0}.osl-rg-group-row__value{font-size:13px;font-weight:700;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-rg-group-row__count{font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:#00000017;margin-right:12px;flex-shrink:0}.osl-rg-group-chevron{width:16px;height:16px;flex-shrink:0;opacity:.75;display:block}.osl-rg-aggregate-row{display:flex;align-items:center;border-top:2px solid #e5e7eb;background:linear-gradient(135deg,#f0fdf4,#f9fafb);flex-shrink:0;min-height:38px}.osl-rg-agg-cell{display:flex;align-items:center;gap:5px;padding:0 12px;border-right:1px solid #e5e7eb;height:38px;flex-shrink:0;overflow:hidden}.osl-rg-agg-cell--checkbox{width:36px;min-width:36px}.osl-rg-agg-label{font-size:9.5px;font-weight:800;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;flex-shrink:0;background:#e5e7eb;padding:1px 4px;border-radius:3px}.osl-rg-agg-value{font-size:12px;font-weight:700;color:#15803d;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-rg-skel{height:12px;border-radius:4px;background:linear-gradient(90deg,#e5e7eb 25%,#f3f4f6,#e5e7eb 75%);background-size:200% 100%;animation:rg-pulse 1.5s ease-in-out infinite}.osl-rg-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 20px;color:#9ca3af;text-align:center}.osl-rg-empty__icon{margin-bottom:18px;opacity:.7}.osl-rg-empty__title{font-size:15px;font-weight:600;color:#374151;margin:0 0 6px}.osl-rg-empty__sub{font-size:13px;color:#9ca3af;margin:0 0 18px;max-width:290px;line-height:1.5}.osl-rg-empty__action{height:34px;padding:0 18px;background:var(--osl-primary, #2563eb);color:#fff;border:none;border-radius:7px;font-size:13px;font-family:inherit;font-weight:600;cursor:pointer;transition:background .15s;box-shadow:0 2px 6px #2563eb4d}.osl-rg-empty__action:hover{background:#1d4ed8}.osl-rg-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--osl-border-color, #e5e7eb);background:linear-gradient(135deg,#f8faff,#f3f4f6);flex-shrink:0;flex-wrap:wrap}.osl-rg-pagination__info{font-size:12px;color:#6b7280;min-width:160px;white-space:nowrap}.osl-rg-pagination__info strong{color:#1f2937;font-weight:600}.osl-rg-pagination__controls{display:flex;align-items:center;gap:3px}.osl-rg-pagination__size{display:flex;align-items:center;gap:7px;min-width:120px;justify-content:flex-end}.osl-rg-pagination__size-label{font-size:11.5px;color:#9ca3af;white-space:nowrap}.osl-rg-page-btn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#fff;color:#374151;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;transition:all .12s;white-space:nowrap}.osl-rg-page-btn svg{display:block}.osl-rg-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af;box-shadow:0 1px 2px #0000000f}.osl-rg-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-rg-page-btn--num{min-width:30px;padding:0;font-size:12.5px}.osl-rg-page-btn--active{background:var(--osl-primary, #2563eb);border-color:var(--osl-primary, #2563eb);color:#fff;font-weight:700;box-shadow:0 2px 5px #2563eb59}.osl-rg-page-btn--active:hover:not(:disabled){background:#1d4ed8;border-color:#1d4ed8}.osl-rg-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:26px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-rg-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#fff;font-size:12px;font-family:inherit;color:#374151;cursor:pointer;outline:none;transition:border-color .15s}.osl-rg-page-size:focus{border-color:var(--osl-primary, #2563eb)}.osl-rg-col-menu{position:fixed;z-index:9999;min-width:210px;background:#fff;border:1px solid #e5e7eb;border-radius:11px;box-shadow:0 12px 36px #00000029,0 3px 10px #00000014;overflow:hidden;animation:rg-fade-up .13s cubic-bezier(.16,1,.3,1)}.osl-rg-col-menu__header{padding:9px 14px 7px;font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f3f4f6;background:#fafbff}.osl-rg-col-menu__divider{height:1px;background:#f3f4f6;margin:2px 0}.osl-rg-col-menu__item{display:flex;align-items:center;gap:9px;width:100%;padding:9px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:none;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;transition:all .1s}.osl-rg-col-menu__item svg{color:#9ca3af;display:block}.osl-rg-col-menu__item:hover{background:#f0f4ff;color:var(--osl-primary, #2563eb);border-left-color:var(--osl-primary, #2563eb)}.osl-rg-col-menu__item:hover svg{color:var(--osl-primary, #2563eb)}.osl-rg-col-menu__item--danger:hover{background:#fff5f5;color:#dc2626;border-left-color:#dc2626}.osl-rg-col-menu__item--danger:hover svg{color:#dc2626}.osl-rg-config-backdrop{position:fixed;inset:0;z-index:9000;background:#0003;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.osl-rg-config-panel{position:fixed;top:0;right:0;bottom:0;z-index:9001;width:300px;background:#fff;border-left:1px solid #e5e7eb;box-shadow:-6px 0 24px #00000021;display:flex;flex-direction:column;animation:rg-slide-in .2s cubic-bezier(.16,1,.3,1)}.osl-rg-config-panel__header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid #e5e7eb;background:linear-gradient(135deg,#f0f4ff,#e8efff);flex-shrink:0}.osl-rg-config-panel__title{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700;color:#1e3a6e}.osl-rg-config-panel__title svg{color:#3b82f6}.osl-rg-config-panel__close{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#9ca3af;cursor:pointer;border-radius:7px;padding:0;transition:all .12s}.osl-rg-config-panel__close svg{width:16px;height:16px;display:block}.osl-rg-config-panel__close:hover{background:#00000012;color:#374151}.osl-rg-config-panel__actions{display:flex;gap:8px;padding:10px 14px;border-bottom:1px solid #f3f4f6;flex-shrink:0;background:#fafbff}.osl-rg-config-hint{padding:6px 16px;font-size:10.5px;color:#b0b7c3;font-style:italic;border-bottom:1px solid #f3f4f6;flex-shrink:0}.osl-rg-config-action{display:inline-flex;align-items:center;gap:5px;height:30px;padding:0 10px;border:1px solid #e5e7eb;border-radius:6px;background:#fff;color:#374151;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;transition:all .12s}.osl-rg-config-action svg{display:block}.osl-rg-config-action:hover{background:#f3f4f6;border-color:#9ca3af}.osl-rg-config-list{flex:1;overflow-y:auto;padding:6px 0}.osl-rg-config-list::-webkit-scrollbar{width:5px}.osl-rg-config-list::-webkit-scrollbar-thumb{background:#e5e7eb;border-radius:4px}.osl-rg-config-list::-webkit-scrollbar-thumb:hover{background:#d1d5db}.osl-rg-config-item{display:flex;align-items:center;gap:9px;padding:7px 14px;transition:background .1s;cursor:grab}.osl-rg-config-item:hover{background:#f9fafb}.osl-rg-config-item:active{cursor:grabbing}.osl-rg-config-item--hidden{opacity:.5}.osl-rg-config-item__label{flex:1;font-size:13px;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.osl-rg-config-item__label--hidden{color:#9ca3af;font-style:italic}.osl-rg-config-item__pin-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:1px solid #e5e7eb;background:#fff;color:#d1d5db;cursor:pointer;padding:0;border-radius:5px;transition:all .12s;flex-shrink:0}.osl-rg-config-item__pin-btn svg{display:block}.osl-rg-config-item__pin-btn:hover,.osl-rg-config-item__pin-btn--active{background:#eff6ff;border-color:#93c5fd;color:var(--osl-primary, #2563eb)}.osl-rg-copy-toast{position:fixed;bottom:24px;left:50%;transform:translate(-50%);z-index:99999;display:flex;align-items:center;gap:7px;background:#1f2937;color:#fff;font-size:12.5px;font-weight:500;padding:8px 18px;border-radius:22px;animation:rg-fade-up .2s ease;box-shadow:0 6px 16px #0000004d;pointer-events:none;white-space:nowrap;font-family:inherit}.osl-rg-copy-toast svg{flex-shrink:0;color:#4ade80}\n"], dependencies: [{ kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.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$1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1$1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i3$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i3$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i3$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i3$1.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$2.UpperCasePipe, name: "uppercase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3559
|
+
}
|
|
3560
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslReportGrid, decorators: [{
|
|
3561
|
+
type: Component,
|
|
3562
|
+
args: [{ selector: 'osl-report-grid', standalone: false, providers: [DatePipe, DecimalPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"osl-rg\" (click)=\"$event.stopPropagation()\">\n\n <!-- \u2550\u2550 TOOLBAR \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-toolbar\">\n <div class=\"osl-rg-toolbar__left\">\n @if (title) { <span class=\"osl-rg-title\">{{ title }}</span> }\n @if (!loading) {\n @if (_filteredTotal !== datasource.length) {\n <span class=\"osl-rg-filter-badge\">\n <span class=\"osl-rg-filter-badge__dot\"></span>\n {{ _filteredTotal | number }} of {{ datasource.length | number }} rows\n </span>\n } @else {\n <span class=\"osl-rg-record-count\">{{ datasource.length | number }} rows</span>\n }\n }\n </div>\n <div class=\"osl-rg-toolbar__right\">\n\n <!-- Global search -->\n <div class=\"osl-rg-global-search\">\n <svg class=\"osl-rg-global-search__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"m21 21-4.35-4.35\"/>\n </svg>\n <input class=\"osl-rg-global-search__input\" placeholder=\"Search all columns\u2026\"\n [(ngModel)]=\"globalSearch\" (ngModelChange)=\"currentPage=1; processData()\" />\n @if (globalSearch) {\n <button class=\"osl-rg-global-search__clear\" (click)=\"globalSearch=''; processData()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n </div>\n\n <!-- Sort active badge -->\n @if (sortStates.size > 0) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--active\" (click)=\"clearSort()\" title=\"Clear sort\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M11 5H4M11 9H4M11 13H4M18 4v16M15 7l3-3 3 3M15 17l3 3 3-3\"/>\n </svg>\n <span>{{ sortStates.size }} sort{{ sortStates.size > 1 ? 's' : '' }}</span>\n <svg class=\"rg-icon rg-icon--xs\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n\n <!-- Filter active badge -->\n @if (hasAnyFilter) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--danger\" (click)=\"clearAllFilters()\" title=\"Clear all filters\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n <span>Filtered</span>\n <svg class=\"rg-icon rg-icon--xs\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n\n <!-- Group toggle -->\n <button class=\"osl-rg-toolbar-btn\" [class.osl-rg-toolbar-btn--on]=\"showGroupPanel\"\n (click)=\"showGroupPanel=!showGroupPanel\" title=\"Group panel\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"2\" y=\"7\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M16 3H8a2 2 0 0 0-2 2v2h12V5a2 2 0 0 0-2-2z\"/>\n </svg>\n <span>Group</span>\n </button>\n\n <!-- Expand / collapse when grouped -->\n @if (activeGroups.length > 0) {\n <button class=\"osl-rg-toolbar-btn\" (click)=\"expandAll()\" title=\"Expand all\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 20 5-5 5 5M7 4l5 5 5-5\"/></svg>\n </button>\n <button class=\"osl-rg-toolbar-btn\" (click)=\"collapseAll()\" title=\"Collapse all\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m7 15 5-5 5 5M7 9l5 5 5 5\"/></svg>\n </button>\n }\n\n <!-- Auto-size all -->\n <button class=\"osl-rg-toolbar-btn\" (click)=\"autoSizeAll()\" title=\"Auto-size all columns\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3M8 15l-5 3 5 3M16 15l5 3-5 3\"/>\n </svg>\n <span>Auto-size</span>\n </button>\n\n <!-- Column config -->\n <button class=\"osl-rg-toolbar-btn\" [class.osl-rg-toolbar-btn--on]=\"showColumnConfig\"\n (click)=\"showColumnConfig=!showColumnConfig; $event.stopPropagation()\" title=\"Column settings\">\n <svg class=\"rg-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M4 6h16M4 12h16M4 18h16\"/>\n <circle cx=\"8\" cy=\"6\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"16\" cy=\"12\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"8\" cy=\"18\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n <span>Columns</span>\n </button>\n\n <!-- Export Excel -->\n @if (exportable) {\n <button class=\"osl-rg-toolbar-btn osl-rg-toolbar-btn--excel\" (click)=\"exportCsv()\" title=\"Export to Excel (CSV)\">\n <svg class=\"rg-icon rg-icon--excel\" viewBox=\"0 0 24 24\">\n <rect width=\"24\" height=\"24\" rx=\"3\" fill=\"#1D6F42\"/>\n <path d=\"M13.5 3H7a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7.5L13.5 3z\" fill=\"#fff\" opacity=\".15\"/>\n <path d=\"M13.5 3v4.5H18L13.5 3z\" fill=\"#fff\" opacity=\".3\"/>\n <path d=\"M8 9.5l2.3 3.5L8 16.5h1.6l1.4-2.3 1.4 2.3H14l-2.3-3.5 2.3-3.5h-1.6l-1.4 2.3-1.4-2.3H8z\" fill=\"#fff\"/>\n </svg>\n <span>Export</span>\n </button>\n }\n </div>\n </div>\n\n <!-- \u2550\u2550 GROUP PANEL \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (showGroupPanel) {\n <div class=\"osl-rg-group-panel\">\n <span class=\"osl-rg-group-panel__label\">Group by:</span>\n @if (activeGroups.length === 0) {\n <span class=\"osl-rg-group-panel__hint\">Use the \u22EE column menu \u2192 \"Group by this\"</span>\n }\n <div class=\"osl-rg-group-chips\" cdkDropList cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"activeGroups\" (cdkDropListDropped)=\"onGroupPanelDrop($event)\">\n @for (key of activeGroups; track key) {\n <div class=\"osl-rg-group-chip\" cdkDrag>\n <svg cdkDragHandle class=\"rg-drag-handle\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.5\"/><circle cx=\"15\" cy=\"5\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"15\" cy=\"12\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"19\" r=\"1.5\"/><circle cx=\"15\" cy=\"19\" r=\"1.5\"/>\n </svg>\n <span>{{ getGroupColLabel(key) }}</span>\n <button class=\"osl-rg-group-chip__remove\" (click)=\"removeGroup(key)\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n </div>\n }\n </div>\n @if (activeGroups.length > 0) {\n <button class=\"osl-rg-group-panel__clear\" (click)=\"clearGroups()\">Clear groups</button>\n }\n </div>\n }\n\n <!-- \u2550\u2550 STICKY HEADER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-header-wrap\" #headerScrollRef>\n <div class=\"osl-rg-header-row\" [style.minWidth.px]=\"totalWidth\">\n\n <!-- Select-all checkbox -->\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-th osl-rg-th--checkbox\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\"\n [checked]=\"allSelected\" [indeterminate]=\"someSelected\"\n (change)=\"toggleSelectAll($any($event.target).checked)\" />\n </div>\n }\n\n <!-- Column headers (drag-to-reorder) -->\n <div class=\"osl-rg-header-cols\" cdkDropList cdkDropListOrientation=\"horizontal\"\n [cdkDropListData]=\"_cols\" (cdkDropListDropped)=\"onColumnReorder($event)\"\n style=\"display:flex;flex:1;min-width:0;\">\n\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-th\"\n [class.osl-rg-th--pinned]=\"col._pinned\"\n [class.osl-rg-th--sorted]=\"getSortState(col.key)\"\n [class.osl-rg-th--filtered]=\"hasActiveFilter(col.key)\"\n [style.width.px]=\"col._width\"\n [style.minWidth.px]=\"col._width\"\n [style.left.px]=\"col._pinned ? col._stickyLeft : null\"\n [style.position]=\"col._pinned ? 'sticky' : 'relative'\"\n [style.zIndex]=\"col._pinned ? 3 : 1\"\n cdkDrag [cdkDragDisabled]=\"col._pinned\"\n (cdkDragStarted)=\"onColumnDragStarted()\"\n (cdkDragEnded)=\"onColumnDragEnded()\"\n (click)=\"onHeaderClick(col, $event)\">\n\n <div *cdkDragPlaceholder class=\"osl-rg-th-drag-placeholder\"></div>\n\n <!-- Pin badge -->\n @if (col._pinned) {\n <span class=\"osl-rg-th-pin-badge\" title=\"Pinned\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"9\" height=\"9\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n </span>\n }\n\n <!-- Label row -->\n <div class=\"osl-rg-th-content\">\n <span class=\"osl-rg-th-label\" [title]=\"col.label\">{{ col.label }}</span>\n <div class=\"osl-rg-th-actions\">\n\n <!-- Sort indicator -->\n @if (col.sortable) {\n <span class=\"osl-rg-sort-icon\"\n [class.osl-rg-sort-icon--asc]=\"getSortState(col.key)?.asc === true\"\n [class.osl-rg-sort-icon--desc]=\"getSortState(col.key)?.asc === false\">\n @if (getSortState(col.key); as s) {\n @if (s.asc) {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 19V5m-7 7 7-7 7 7\"/></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14m7-7-7 7-7-7\"/></svg>\n }\n @if (sortStates.size > 1) { <sup class=\"osl-rg-sort-badge\">{{ s.index + 1 }}</sup> }\n } @else {\n <svg class=\"osl-rg-sort-idle\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 9l4-4 4 4M16 15l-4 4-4-4\"/></svg>\n }\n </span>\n }\n\n <!-- Excel filter button -->\n @if (col.filterable) {\n <button class=\"osl-rg-filter-btn\" [class.osl-rg-filter-btn--active]=\"hasActiveFilter(col.key)\"\n (click)=\"openExcelFilter(col.key, $event)\" title=\"Filter\">\n @if (hasActiveFilter(col.key)) {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n } @else {\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3\"/></svg>\n }\n </button>\n }\n\n <!-- Column menu -->\n <button class=\"osl-rg-col-menu-btn\" (click)=\"openColumnMenu(col.key, $event)\" title=\"Column options\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><circle cx=\"12\" cy=\"5\" r=\"2\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/><circle cx=\"12\" cy=\"19\" r=\"2\"/></svg>\n </button>\n\n </div>\n </div>\n\n <!-- Per-column search -->\n @if (col.searchable) {\n <div class=\"osl-rg-col-search\" (click)=\"$event.stopPropagation()\">\n <svg class=\"osl-rg-col-search__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\"/><path d=\"m18 18-3.5-3.5\"/>\n </svg>\n <input class=\"osl-rg-col-search__input\" [placeholder]=\"col.label + '\u2026'\"\n [ngModel]=\"columnSearch[col.key]\" (ngModelChange)=\"onColumnSearch(col.key, $event)\" />\n @if (columnSearch[col.key]) {\n <button class=\"osl-rg-col-search__clear\" (click)=\"columnSearch[col.key]=''; processData()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n }\n </div>\n }\n\n <!-- Resize handle -->\n @if (col.resizable) {\n <div class=\"osl-rg-resize-handle\" (mousedown)=\"startResize(col, $event)\" (dblclick)=\"autoSizeColumn(col, $event)\" (click)=\"$event.stopPropagation()\"></div>\n }\n\n\n </div><!-- /osl-rg-th -->\n }\n\n </div><!-- /header-cols -->\n </div><!-- /header-row -->\n </div><!-- /header-wrap -->\n\n <!-- \u2550\u2550 SCROLLABLE BODY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <div class=\"osl-rg-body\" [style.height]=\"tableHeight\" #bodyScrollRef (scroll)=\"onBodyScroll($event)\">\n <div class=\"osl-rg-inner\" [style.minWidth.px]=\"totalWidth\">\n\n <!-- Skeleton loading -->\n @if (loading) {\n @for (sk of skeletonRows; track $index) {\n <div class=\"osl-rg-row osl-rg-row--skeleton\" [style.height.px]=\"rowHeight\">\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-td osl-rg-td--checkbox\"><div class=\"osl-rg-skel\" style=\"width:14px;height:14px;border-radius:3px;\"></div></div>\n }\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-td\" [style.width.px]=\"col._width\" [style.minWidth.px]=\"col._width\">\n <div class=\"osl-rg-skel\" [style.width]=\"$index % 3 === 0 ? '55%' : $index % 3 === 1 ? '80%' : '65%'\"></div>\n </div>\n }\n </div>\n }\n\n } @else if (isEmpty) {\n <!-- Empty state -->\n <div class=\"osl-rg-empty\">\n <div class=\"osl-rg-empty__icon\">\n <svg viewBox=\"0 0 48 48\" fill=\"none\" stroke=\"#d1d5db\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"6\" y=\"6\" width=\"36\" height=\"36\" rx=\"4\"/>\n <path d=\"M6 18h36M6 30h36M18 6v36\"/>\n </svg>\n </div>\n <p class=\"osl-rg-empty__title\">No records found</p>\n <p class=\"osl-rg-empty__sub\">\n @if (hasAnyFilter) { Try adjusting your filters or search terms }\n @else { No data to display }\n </p>\n @if (hasAnyFilter) {\n <button class=\"osl-rg-empty__action\" (click)=\"clearAllFilters()\">Clear all filters</button>\n }\n </div>\n\n } @else {\n <!-- Data rows -->\n @for (row of flatRows; track trackByRow($index, row)) {\n\n @if (row._type === 'group') {\n <div class=\"osl-rg-group-row\"\n [class.osl-rg-group-row--l0]=\"asGroupRow(row)._level === 0\"\n [class.osl-rg-group-row--l1]=\"asGroupRow(row)._level === 1\"\n [class.osl-rg-group-row--l2]=\"asGroupRow(row)._level >= 2\"\n [style.height.px]=\"rowHeight\"\n [style.paddingLeft.px]=\"asGroupRow(row)._level * 20 + 12\"\n (click)=\"toggleGroup(asGroupRow(row)._path)\">\n @if (asGroupRow(row)._expanded) {\n <svg class=\"osl-rg-group-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m6 9 6 6 6-6\"/></svg>\n } @else {\n <svg class=\"osl-rg-group-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m9 18 6-6-6-6\"/></svg>\n }\n <span class=\"osl-rg-group-row__key\">{{ asGroupRow(row)._colLabel }}:</span>\n <span class=\"osl-rg-group-row__value\">{{ asGroupRow(row)._label }}</span>\n <span class=\"osl-rg-group-row__count\">{{ asGroupRow(row)._count | number }} rows</span>\n </div>\n\n } @else {\n <div class=\"osl-rg-row\"\n [class.osl-rg-row--striped]=\"striped && asDataRow(row)._rowIndex % 2 === 1\"\n [class.osl-rg-row--selected]=\"selectedRows.has(asDataRow(row)._data)\"\n [style.height.px]=\"rowHeight\"\n (click)=\"onRowClick(asDataRow(row)._data, $event)\">\n\n @if (rowSelection === 'multiple') {\n <div class=\"osl-rg-td osl-rg-td--checkbox\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\"\n [checked]=\"selectedRows.has(asDataRow(row)._data)\"\n (change)=\"toggleRowSelect(asDataRow(row)._data, $event)\" />\n </div>\n }\n\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-td\"\n [class.osl-rg-td--pinned]=\"col._pinned\"\n [ngClass]=\"getCellClass(asDataRow(row)._data, col)\"\n [style.width.px]=\"col._width\"\n [style.minWidth.px]=\"col._width\"\n [style.left.px]=\"col._pinned ? col._stickyLeft : null\"\n [style.position]=\"col._pinned ? 'sticky' : 'relative'\"\n [style.zIndex]=\"col._pinned ? 2 : 0\"\n [style.textAlign]=\"col.align ?? 'left'\"\n (dblclick)=\"copyCell(getCellDisplay(asDataRow(row)._data, col), $event)\"\n [title]=\"getCellDisplay(asDataRow(row)._data, col)\">\n <span class=\"osl-rg-cell-text\">{{ getCellDisplay(asDataRow(row)._data, col) }}</span>\n </div>\n }\n\n </div>\n }\n\n }\n }\n\n </div><!-- /osl-rg-inner -->\n </div><!-- /osl-rg-body -->\n\n <!-- \u2550\u2550 AGGREGATE FOOTER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (showAggregates && hasAnyAggregate() && !loading) {\n <div class=\"osl-rg-aggregate-row\" [style.minWidth.px]=\"totalWidth\">\n @if (rowSelection === 'multiple') { <div class=\"osl-rg-agg-cell osl-rg-agg-cell--checkbox\"></div> }\n @for (col of visibleCols; track col.key) {\n <div class=\"osl-rg-agg-cell\" [style.width.px]=\"col._width\" [style.minWidth.px]=\"col._width\" [style.textAlign]=\"col.align ?? 'left'\">\n @if (col.aggregate) {\n <span class=\"osl-rg-agg-label\">{{ col.aggregate | uppercase }}</span>\n <span class=\"osl-rg-agg-value\">{{ getAggregate(col) }}</span>\n }\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550 PAGINATION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (isPaginated) {\n <div class=\"osl-rg-pagination\">\n <span class=\"osl-rg-pagination__info\">\n @if (loading) { Loading\u2026 }\n @else if (_total > 0) { Showing <strong>{{ startRecord | number }}\u2013{{ endRecord | number }}</strong> of <strong>{{ _total | number }}</strong> records }\n @else { No records }\n </span>\n <div class=\"osl-rg-pagination__controls\">\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(1)\" [disabled]=\"currentPage===1||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m11 17-5-5 5-5M18 17l-5-5 5-5\"/></svg>\n </button>\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(currentPage-1)\" [disabled]=\"currentPage===1||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m15 18-6-6 6-6\"/></svg>\n Prev\n </button>\n @for (page of pageNumbers; track $index) {\n @if (page === -1) { <span class=\"osl-rg-page-ellipsis\">\u2026</span> }\n @else {\n <button class=\"osl-rg-page-btn osl-rg-page-btn--num\" [class.osl-rg-page-btn--active]=\"page===currentPage\"\n [disabled]=\"loading\" (click)=\"goToPage(page)\">{{ page }}</button>\n }\n }\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(currentPage+1)\" [disabled]=\"currentPage===totalPages||loading\">\n Next\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m9 18 6-6-6-6\"/></svg>\n </button>\n <button class=\"osl-rg-page-btn osl-rg-page-btn--nav\" (click)=\"goToPage(totalPages)\" [disabled]=\"currentPage===totalPages||loading\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"m13 17 5-5-5-5M6 17l5-5-5-5\"/></svg>\n </button>\n </div>\n <div class=\"osl-rg-pagination__size\">\n <span class=\"osl-rg-pagination__size-label\">Rows per page</span>\n <select class=\"osl-rg-page-size\" [ngModel]=\"pageSize\" (ngModelChange)=\"onPageSizeChange($event)\" [disabled]=\"loading\">\n @for (opt of pageSizeOptions; track opt) { <option [value]=\"opt\">{{ opt }}</option> }\n </select>\n </div>\n </div>\n }\n\n</div><!-- /osl-rg -->\n\n\n<!-- \u2550\u2550 EXCEL FILTER DROPDOWN (fixed, outside overflow containers) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (openFilterKey && excelFilterState[openFilterKey]) {\n <div class=\"osl-rg-excel-filter\"\n [style.top.px]=\"filterDropdownPos.top\"\n [style.left.px]=\"filterDropdownPos.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-excel-filter__header\">\n <div class=\"osl-rg-excel-filter__title\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"12\" height=\"12\" style=\"flex-shrink:0;\"><path d=\"M22 3H2l8 9.46V19l4 2V12.46z\"/></svg>\n Filter: {{ getColumnByKey(openFilterKey)?.label }}\n </div>\n @if (hasActiveFilter(openFilterKey)) {\n <button class=\"osl-rg-excel-filter__clear-btn\" (click)=\"clearExcelFilter(openFilterKey!)\">Clear</button>\n }\n </div>\n <div class=\"osl-rg-excel-filter__search\">\n <input class=\"osl-rg-excel-filter__search-input\" placeholder=\"Search values\u2026\"\n [(ngModel)]=\"excelFilterState[openFilterKey].search\" />\n </div>\n <div class=\"osl-rg-excel-filter__select-all\">\n <label class=\"osl-rg-excel-filter__item osl-rg-excel-filter__item--all\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [checked]=\"isAllExcelChecked(openFilterKey)\"\n (change)=\"toggleAllExcelFilter(openFilterKey!, $any($event.target).checked)\" />\n <span class=\"osl-rg-excel-filter__item-label\">(Select All)</span>\n </label>\n </div>\n <div class=\"osl-rg-excel-filter__list\">\n @for (item of getFilteredExcelItems(openFilterKey); track item.value) {\n <label class=\"osl-rg-excel-filter__item\">\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [(ngModel)]=\"item.checked\" />\n <span class=\"osl-rg-excel-filter__item-label\">{{ item.label }}</span>\n </label>\n }\n </div>\n <div class=\"osl-rg-excel-filter__footer\">\n <button class=\"osl-rg-excel-filter__apply\" (click)=\"applyExcelFilter(openFilterKey!)\">Apply Filter</button>\n <button class=\"osl-rg-excel-filter__cancel\" (click)=\"openFilterKey=null\">Cancel</button>\n </div>\n </div>\n}\n\n\n<!-- \u2550\u2550 COLUMN CONTEXT MENU (fixed) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (columnMenuKey && getColumnByKey(columnMenuKey); as menuCol) {\n <div class=\"osl-rg-col-menu\"\n [style.top.px]=\"columnMenuPos.top\" [style.left.px]=\"columnMenuPos.left\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-col-menu__header\">{{ menuCol.label }}</div>\n @if (menuCol.sortable) {\n <button class=\"osl-rg-col-menu__item\"\n (click)=\"sortStates.clear(); sortStates.set(menuCol.key,{key:menuCol.key,asc:true,index:0}); sortCounter=1; processData(); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 19V5m-7 7 7-7 7 7\"/></svg>\n Sort A \u2192 Z\n </button>\n <button class=\"osl-rg-col-menu__item\"\n (click)=\"sortStates.clear(); sortStates.set(menuCol.key,{key:menuCol.key,asc:false,index:0}); sortCounter=1; processData(); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 5v14m7-7-7 7-7-7\"/></svg>\n Sort Z \u2192 A\n </button>\n }\n @if (menuCol.groupable) {\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item\" (click)=\"addGroup(menuCol.key); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"7\" width=\"20\" height=\"14\" rx=\"2\"/><path d=\"M16 3H8a2 2 0 0 0-2 2v2h12V5a2 2 0 0 0-2-2z\"/></svg>\n Group by this\n </button>\n }\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item\" (click)=\"toggleColumnPin(menuCol)\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n {{ menuCol._pinned ? 'Unpin column' : 'Pin to left' }}\n </button>\n <button class=\"osl-rg-col-menu__item\" (click)=\"autoSizeColumn(menuCol); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3M8 15l-5 3 5 3M16 15l5 3-5 3\"/></svg>\n Auto-size column\n </button>\n <div class=\"osl-rg-col-menu__divider\"></div>\n <button class=\"osl-rg-col-menu__item osl-rg-col-menu__item--danger\" (click)=\"toggleColumnVisibility(menuCol); closeColumnMenu()\">\n <svg class=\"rg-icon rg-icon--sm\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24M1 1l22 22\"/>\n </svg>\n Hide column\n </button>\n </div>\n}\n\n\n<!-- \u2550\u2550 COLUMN CONFIG SIDE DRAWER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (showColumnConfig) {\n <div class=\"osl-rg-config-backdrop\" (click)=\"showColumnConfig=false\"></div>\n <div class=\"osl-rg-config-panel\" (click)=\"$event.stopPropagation()\">\n <div class=\"osl-rg-config-panel__header\">\n <div class=\"osl-rg-config-panel__title\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"16\" height=\"16\">\n <path d=\"M4 6h16M4 12h16M4 18h16\"/>\n <circle cx=\"8\" cy=\"6\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"16\" cy=\"12\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n <circle cx=\"8\" cy=\"18\" r=\"2\" fill=\"white\" stroke=\"currentColor\" stroke-width=\"2\"/>\n </svg>\n Column Settings\n </div>\n <button class=\"osl-rg-config-panel__close\" (click)=\"showColumnConfig=false\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>\n </button>\n </div>\n <div class=\"osl-rg-config-panel__actions\">\n <button class=\"osl-rg-config-action\" (click)=\"autoSizeAll()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"M21 6H3M21 18H3\"/><path d=\"M8 3 3 6l5 3M16 3l5 3-5 3\"/></svg>\n Auto-size all\n </button>\n <button class=\"osl-rg-config-action\" (click)=\"showAllColumns()\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><path d=\"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></svg>\n Show all\n </button>\n </div>\n <div class=\"osl-rg-config-hint\">Drag to reorder \u00B7 Toggle to show/hide \u00B7 Pin to freeze</div>\n <div class=\"osl-rg-config-list\" cdkDropList (cdkDropListDropped)=\"onConfigColumnReorder($event)\">\n @for (col of _cols; track col.key) {\n <div class=\"osl-rg-config-item\" cdkDrag [class.osl-rg-config-item--hidden]=\"col._hidden\">\n <svg cdkDragHandle class=\"rg-drag-handle\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <circle cx=\"9\" cy=\"5\" r=\"1.5\"/><circle cx=\"15\" cy=\"5\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"12\" r=\"1.5\"/><circle cx=\"15\" cy=\"12\" r=\"1.5\"/>\n <circle cx=\"9\" cy=\"19\" r=\"1.5\"/><circle cx=\"15\" cy=\"19\" r=\"1.5\"/>\n </svg>\n <input type=\"checkbox\" class=\"osl-rg-checkbox\" [checked]=\"!col._hidden\" (change)=\"toggleColumnVisibility(col)\" />\n <span class=\"osl-rg-config-item__label\" [class.osl-rg-config-item__label--hidden]=\"col._hidden\">{{ col.label }}</span>\n <button class=\"osl-rg-config-item__pin-btn\" (click)=\"toggleColumnPin(col)\"\n [class.osl-rg-config-item__pin-btn--active]=\"col._pinned\"\n [title]=\"col._pinned ? 'Unpin' : 'Pin to left'\">\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" width=\"12\" height=\"12\"><path d=\"M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z\"/></svg>\n </button>\n </div>\n }\n </div>\n </div>\n}\n\n\n<!-- \u2550\u2550 COPY TOAST \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n@if (copiedCell !== null) {\n <div class=\"osl-rg-copy-toast\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" width=\"13\" height=\"13\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Copied to clipboard\n </div>\n}\n", styles: ["@keyframes rg-pulse{0%,to{opacity:1}50%{opacity:.4}}@keyframes rg-fade-up{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes rg-slide-in{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.rg-icon{width:15px;height:15px;flex-shrink:0;display:block}.rg-icon--sm{width:14px;height:14px}.rg-icon--xs{width:12px;height:12px}.rg-icon--excel{width:18px;height:18px}.rg-drag-handle{width:14px;height:14px;flex-shrink:0;color:#d1d5db;cursor:grab;display:block}.rg-drag-handle:active{cursor:grabbing}.osl-rg{position:relative;display:flex;flex-direction:column;width:100%;font-family:inherit;font-size:13px;color:#111827;background:#fff;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:10px;overflow:hidden;box-shadow:0 2px 8px #00000012,0 1px 2px #0000000a;-webkit-user-select:none;user-select:none}.osl-rg-toolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:10px 14px;background:linear-gradient(135deg,#f8faff,#f3f4f6);border-bottom:1px solid var(--osl-border-color, #e5e7eb);flex-shrink:0;flex-wrap:wrap}.osl-rg-toolbar__left{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.osl-rg-toolbar__right{display:flex;align-items:center;gap:6px;flex-wrap:wrap}.osl-rg-title{font-size:14px;font-weight:700;color:#111827;letter-spacing:-.01em}.osl-rg-record-count{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:#9ca3af;font-weight:500}.osl-rg-record-count svg{color:#d1d5db}.osl-rg-filter-badge{display:inline-flex;align-items:center;gap:5px;padding:2px 9px 2px 6px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:12px;font-size:11px;color:#1d4ed8;font-weight:600}.osl-rg-filter-badge__dot{width:6px;height:6px;border-radius:50%;background:#2563eb;animation:rg-pulse 1.5s ease-in-out infinite;flex-shrink:0}.osl-rg-toolbar-btn{display:inline-flex;align-items:center;gap:5px;height:32px;padding:0 11px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:7px;background:#fff;color:#5a6478;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;white-space:nowrap;transition:all .15s ease}.osl-rg-toolbar-btn svg{flex-shrink:0}.osl-rg-toolbar-btn:hover{background:#f3f4f6;color:#1f2937;border-color:#c4c9d4;box-shadow:0 1px 3px #0000000f}.osl-rg-toolbar-btn--on{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8}.osl-rg-toolbar-btn--active{background:#eff6ff;border-color:#93c5fd;color:#1d4ed8;font-weight:600}.osl-rg-toolbar-btn--danger{background:#fff5f5;border-color:#fca5a5;color:#dc2626}.osl-rg-toolbar-btn--danger:hover{background:#fee2e2;border-color:#f87171}.osl-rg-toolbar-btn--excel{background:linear-gradient(135deg,#1a7a3e,#1d6f42);border-color:#166534;color:#fff;font-weight:600;box-shadow:0 1px 3px #1665344d}.osl-rg-toolbar-btn--excel:hover{background:linear-gradient(135deg,#166534,#145a2c);border-color:#14532d;color:#fff;box-shadow:0 2px 6px #16653466}.osl-rg-global-search{position:relative;display:inline-flex;align-items:center}.osl-rg-global-search__icon{position:absolute;left:9px;width:14px;height:14px;color:#9ca3af;pointer-events:none;display:block}.osl-rg-global-search__input{height:32px;padding:0 30px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:7px;background:#fff;font-size:12.5px;font-family:inherit;color:#111827;outline:none;width:210px;transition:border-color .15s,width .2s,box-shadow .15s}.osl-rg-global-search__input::placeholder{color:#b0b7c3}.osl-rg-global-search__input:focus{border-color:var(--osl-primary, #2563eb);width:260px;box-shadow:0 0 0 3px #2563eb1a}.osl-rg-global-search__clear{position:absolute;right:6px;display:flex;align-items:center;justify-content:center;width:18px;height:18px;border:none;background:transparent;color:#9ca3af;cursor:pointer;padding:0;border-radius:4px;transition:all .12s}.osl-rg-global-search__clear svg{width:12px;height:12px}.osl-rg-global-search__clear:hover{color:#374151;background:#0000000d}.osl-rg-group-panel{display:flex;align-items:center;gap:8px;padding:6px 14px;background:linear-gradient(135deg,#fafbff,#f5f7ff);border-bottom:1px dashed #dde5ff;min-height:40px;flex-shrink:0;flex-wrap:wrap}.osl-rg-group-panel__label{display:flex;align-items:center;gap:5px;font-size:11px;font-weight:700;color:#6b7280;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;flex-shrink:0}.osl-rg-group-panel__label svg{color:#9ca3af}.osl-rg-group-panel__hint{font-size:11px;color:#b0b7c3;font-style:italic}.osl-rg-group-panel__clear{margin-left:auto;font-size:11px;color:#6b7280;background:none;border:none;cursor:pointer;text-decoration:underline;flex-shrink:0;padding:0;font-family:inherit}.osl-rg-group-panel__clear:hover{color:#dc2626}.osl-rg-group-chips{display:flex;gap:6px;flex-wrap:wrap;align-items:center;flex:1;min-height:26px}.osl-rg-group-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 6px 3px 5px;background:linear-gradient(135deg,#dbeafe,#eff6ff);border:1px solid #bfdbfe;border-radius:20px;font-size:12px;font-weight:600;color:#1e40af;cursor:grab;-webkit-user-select:none;user-select:none;box-shadow:0 1px 2px #2563eb1a;transition:box-shadow .1s}.osl-rg-group-chip:active{cursor:grabbing;box-shadow:0 2px 6px #2563eb33}.osl-rg-group-chip .rg-drag-handle{color:#93c5fd}.osl-rg-group-chip__remove{display:flex;align-items:center;justify-content:center;width:16px;height:16px;border:none;background:transparent;cursor:pointer;padding:0;border-radius:50%;transition:all .1s}.osl-rg-group-chip__remove svg{width:10px;height:10px;color:#93c5fd}.osl-rg-group-chip__remove:hover{background:#dbeafe}.osl-rg-group-chip__remove:hover svg{color:#1e40af}.osl-rg-table-area{display:flex;flex-direction:column;flex:1 1 0;overflow:hidden;position:relative;min-height:0}.osl-rg-header-wrap{overflow:hidden;flex-shrink:0;border-bottom:2px solid #dde5ff;background:linear-gradient(180deg,#f0f4ff,#e8efff);z-index:10;position:relative}.osl-rg-header-row{display:flex;align-items:stretch}.osl-rg-header-cols{display:flex}.osl-rg-th{position:relative;display:flex;flex-direction:column;background:transparent;border-right:1px solid #dde5ff;cursor:pointer;flex-shrink:0;transition:background .12s;overflow:visible}.osl-rg-th:last-child{border-right:none}.osl-rg-th:hover{background:#2563eb0f}.osl-rg-th--pinned{background:linear-gradient(180deg,#e8efff,#dde8ff);border-right:1px solid #bfdbfe;z-index:3}.osl-rg-th--pinned:after{content:\"\";position:absolute;right:-5px;top:0;bottom:0;width:5px;background:linear-gradient(to right,rgba(37,99,235,.14),transparent);pointer-events:none;z-index:4}.osl-rg-th--sorted{background:#6366f114}.osl-rg-th--filtered{background:#f59e0b12}.osl-rg-th-drag-placeholder{background:#e0e7ff;border:2px dashed #6366f1;border-radius:4px;opacity:.6}.osl-rg-th-pin-badge{position:absolute;top:3px;right:24px;color:var(--osl-primary, #2563eb);opacity:.6;display:flex;align-items:center}.osl-rg-th-content{display:flex;align-items:center;justify-content:space-between;gap:4px;padding:0 8px 0 10px;height:36px;min-height:36px}.osl-rg-th-label{font-size:11.5px;font-weight:700;color:#1e3a6e;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}.osl-rg-th-actions{display:flex;align-items:center;gap:2px;flex-shrink:0}.osl-rg-sort-icon{display:inline-flex;align-items:center;position:relative;color:#9ca3af;transition:color .12s;width:14px;height:14px}.osl-rg-sort-icon svg{width:13px;height:13px;display:block}.osl-rg-sort-icon--asc,.osl-rg-sort-icon--desc{color:var(--osl-primary, #2563eb)}.osl-rg-sort-idle{opacity:.3}.osl-rg-sort-badge{position:absolute;top:-4px;right:-6px;font-size:8px;font-weight:700;background:var(--osl-primary, #2563eb);color:#fff;border-radius:50%;width:11px;height:11px;display:flex;align-items:center;justify-content:center;line-height:1}.osl-rg-filter-btn,.osl-rg-col-menu-btn{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border:none;background:transparent;color:#b0b7c3;cursor:pointer;padding:0;border-radius:5px;transition:all .12s}.osl-rg-filter-btn svg,.osl-rg-col-menu-btn svg{width:13px;height:13px;display:block}.osl-rg-filter-btn:hover,.osl-rg-col-menu-btn:hover{background:#00000012;color:#374151}.osl-rg-filter-btn--active{color:var(--osl-primary, #2563eb)!important;background:#2563eb1f!important}.osl-rg-col-search{position:relative;display:flex;align-items:center;padding:3px 6px;border-top:1px solid #e0e7ff;background:#ffffff80}.osl-rg-col-search__icon{position:absolute;left:10px;width:12px;height:12px;display:block;pointer-events:none;color:#c4cad6}.osl-rg-col-search__input{width:100%;height:23px;padding:0 18px 0 22px;border:1px solid #dde5ff;border-radius:5px;font-size:11px;font-family:inherit;background:#fff;color:#111827;outline:none;transition:border-color .12s,box-shadow .12s}.osl-rg-col-search__input::placeholder{color:#c4cad6}.osl-rg-col-search__input:focus{border-color:var(--osl-primary, #2563eb);box-shadow:0 0 0 2px #2563eb1a}.osl-rg-col-search__clear{position:absolute;right:8px;display:flex;align-items:center;width:14px;height:14px;border:none;background:transparent;color:#9ca3af;cursor:pointer;padding:0;border-radius:3px;transition:all .1s}.osl-rg-col-search__clear svg{width:10px;height:10px;display:block}.osl-rg-col-search__clear:hover{color:#374151;background:#0000000f}.osl-rg-resize-handle{position:absolute;right:0;top:0;bottom:0;width:6px;cursor:col-resize;z-index:5}.osl-rg-resize-handle:after{content:\"\";position:absolute;right:2px;top:15%;bottom:15%;width:2px;border-radius:2px;background:#a5b4fc;opacity:0;transition:opacity .15s}.osl-rg-resize-handle:hover:after{opacity:1}.osl-rg-th:hover .osl-rg-resize-handle:after{opacity:.4}.osl-rg-excel-filter{position:fixed;z-index:9999;width:250px;background:#fff;border:1px solid #e5e7eb;border-radius:10px;box-shadow:0 12px 32px #00000026,0 2px 8px #00000012;animation:rg-fade-up .14s ease;overflow:hidden}.osl-rg-excel-filter__header{display:flex;align-items:center;justify-content:space-between;padding:9px 12px;background:linear-gradient(135deg,#f0f4ff,#e8efff);border-bottom:1px solid #dde5ff}.osl-rg-excel-filter__title{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:#1e3a6e;text-transform:uppercase;letter-spacing:.06em}.osl-rg-excel-filter__clear-btn{font-size:11px;color:#dc2626;background:none;border:none;cursor:pointer;font-weight:600;padding:2px 6px;border-radius:4px;font-family:inherit;transition:background .1s}.osl-rg-excel-filter__clear-btn:hover{background:#fee2e2}.osl-rg-excel-filter__search{padding:7px 8px;border-bottom:1px solid #f3f4f6;position:relative}.osl-rg-excel-filter__search-input{width:100%;height:28px;padding:0 8px 0 28px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .12s;color:#111827}.osl-rg-excel-filter__search-input::placeholder{color:#b0b7c3}.osl-rg-excel-filter__search-input:focus{border-color:var(--osl-primary, #2563eb)}.osl-rg-excel-filter__select-all{padding:0 8px;border-bottom:2px solid #f3f4f6;background:#fafbff}.osl-rg-excel-filter__list{max-height:210px;overflow-y:auto;padding:3px 8px}.osl-rg-excel-filter__list::-webkit-scrollbar{width:5px}.osl-rg-excel-filter__list::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:4px}.osl-rg-excel-filter__list::-webkit-scrollbar-thumb:hover{background:#9ca3af}.osl-rg-excel-filter__item{display:flex;align-items:center;gap:8px;padding:5px 4px;font-size:12.5px;color:#374151;cursor:pointer;border-radius:5px;transition:background .1s}.osl-rg-excel-filter__item:hover{background:#f3f4f6}.osl-rg-excel-filter__item--all{font-weight:600;color:#1f2937;padding:6px 4px}.osl-rg-excel-filter__item-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.osl-rg-excel-filter__footer{display:flex;gap:6px;padding:8px 10px;border-top:1px solid #f3f4f6;background:#f9fafb}.osl-rg-excel-filter__apply{flex:1;height:30px;border:none;border-radius:6px;font-size:12px;font-family:inherit;font-weight:600;cursor:pointer;background:var(--osl-primary, #2563eb);color:#fff;transition:background .12s}.osl-rg-excel-filter__apply:hover{background:#1d4ed8}.osl-rg-excel-filter__cancel{height:30px;padding:0 14px;border:1px solid #e5e7eb;border-radius:6px;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;background:#fff;color:#6b7280;transition:all .12s}.osl-rg-excel-filter__cancel:hover{background:#f3f4f6;border-color:#9ca3af}.osl-rg-th--checkbox,.osl-rg-td--checkbox{display:flex;align-items:center;justify-content:center;width:36px;min-width:36px;flex-shrink:0;border-right:1px solid #dde5ff}.osl-rg-checkbox{width:14px;height:14px;cursor:pointer;accent-color:var(--osl-primary, #2563eb);flex-shrink:0}.osl-rg-body{overflow-x:auto;overflow-y:auto;flex-shrink:0}.osl-rg-body::-webkit-scrollbar{width:8px;height:8px}.osl-rg-body::-webkit-scrollbar-track{background:#f9fafb}.osl-rg-body::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:4px}.osl-rg-body::-webkit-scrollbar-thumb:hover{background:#9ca3af}.osl-rg-body::-webkit-scrollbar-corner{background:#f9fafb}.osl-rg-inner{display:block}.osl-rg-row{display:flex;align-items:stretch;border-bottom:1px solid #f3f4f6;transition:background .08s;cursor:default}.osl-rg-row:hover{background:#f0f5ff!important}.osl-rg-row--striped{background:#fafbff}.osl-rg-row--selected{background:#eff6ff!important;border-bottom-color:#dbeafe}.osl-rg-row--skeleton{pointer-events:none}.osl-rg-td{display:flex;align-items:center;padding:0 12px;border-right:1px solid #f0f2f7;overflow:hidden;background:inherit;flex-shrink:0}.osl-rg-td:last-child{border-right:none}.osl-rg-td--pinned{background:#fff;border-right:1px solid #e0e7ff;z-index:2}.osl-rg-td--pinned:after{content:\"\";position:absolute;right:-5px;top:0;bottom:0;width:5px;background:linear-gradient(to right,rgba(37,99,235,.07),transparent);pointer-events:none}.osl-rg-cell-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block;width:100%;font-size:13px;color:#1f2937;line-height:1.4}.rg-profit-pos .osl-rg-cell-text{color:#15803d;font-weight:600}.rg-profit-neg .osl-rg-cell-text{color:#dc2626;font-weight:600}.rg-status-delivered .osl-rg-cell-text{color:#15803d;font-weight:600}.rg-status-shipped .osl-rg-cell-text{color:#2563eb;font-weight:600}.rg-status-processing .osl-rg-cell-text{color:#d97706;font-weight:600}.rg-status-pending .osl-rg-cell-text{color:#6b7280;font-weight:500}.rg-status-cancelled .osl-rg-cell-text{color:#dc2626;font-weight:500;text-decoration:line-through;opacity:.75}.osl-rg-group-row{display:flex;align-items:center;gap:7px;border-bottom:1px solid #e5e7eb;font-weight:600;cursor:pointer;flex-shrink:0;transition:filter .1s}.osl-rg-group-row--l0{background:linear-gradient(135deg,#eff6ff,#dbeafe);color:#1e40af;border-left:4px solid #3b82f6}.osl-rg-group-row--l1{background:linear-gradient(135deg,#f5f3ff,#ede9fe);color:#5b21b6;border-left:4px solid #8b5cf6}.osl-rg-group-row--l2{background:linear-gradient(135deg,#fff7ed,#fef3c7);color:#92400e;border-left:4px solid #f59e0b}.osl-rg-group-row:hover{filter:brightness(.97)}.osl-rg-group-row__key{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;opacity:.65;flex-shrink:0}.osl-rg-group-row__value{font-size:13px;font-weight:700;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-rg-group-row__count{font-size:11px;font-weight:600;padding:2px 9px;border-radius:12px;background:#00000017;margin-right:12px;flex-shrink:0}.osl-rg-group-chevron{width:16px;height:16px;flex-shrink:0;opacity:.75;display:block}.osl-rg-aggregate-row{display:flex;align-items:center;border-top:2px solid #e5e7eb;background:linear-gradient(135deg,#f0fdf4,#f9fafb);flex-shrink:0;min-height:38px}.osl-rg-agg-cell{display:flex;align-items:center;gap:5px;padding:0 12px;border-right:1px solid #e5e7eb;height:38px;flex-shrink:0;overflow:hidden}.osl-rg-agg-cell--checkbox{width:36px;min-width:36px}.osl-rg-agg-label{font-size:9.5px;font-weight:800;color:#9ca3af;text-transform:uppercase;letter-spacing:.06em;flex-shrink:0;background:#e5e7eb;padding:1px 4px;border-radius:3px}.osl-rg-agg-value{font-size:12px;font-weight:700;color:#15803d;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.osl-rg-skel{height:12px;border-radius:4px;background:linear-gradient(90deg,#e5e7eb 25%,#f3f4f6,#e5e7eb 75%);background-size:200% 100%;animation:rg-pulse 1.5s ease-in-out infinite}.osl-rg-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:64px 20px;color:#9ca3af;text-align:center}.osl-rg-empty__icon{margin-bottom:18px;opacity:.7}.osl-rg-empty__title{font-size:15px;font-weight:600;color:#374151;margin:0 0 6px}.osl-rg-empty__sub{font-size:13px;color:#9ca3af;margin:0 0 18px;max-width:290px;line-height:1.5}.osl-rg-empty__action{height:34px;padding:0 18px;background:var(--osl-primary, #2563eb);color:#fff;border:none;border-radius:7px;font-size:13px;font-family:inherit;font-weight:600;cursor:pointer;transition:background .15s;box-shadow:0 2px 6px #2563eb4d}.osl-rg-empty__action:hover{background:#1d4ed8}.osl-rg-pagination{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-top:1px solid var(--osl-border-color, #e5e7eb);background:linear-gradient(135deg,#f8faff,#f3f4f6);flex-shrink:0;flex-wrap:wrap}.osl-rg-pagination__info{font-size:12px;color:#6b7280;min-width:160px;white-space:nowrap}.osl-rg-pagination__info strong{color:#1f2937;font-weight:600}.osl-rg-pagination__controls{display:flex;align-items:center;gap:3px}.osl-rg-pagination__size{display:flex;align-items:center;gap:7px;min-width:120px;justify-content:flex-end}.osl-rg-pagination__size-label{font-size:11.5px;color:#9ca3af;white-space:nowrap}.osl-rg-page-btn{display:inline-flex;align-items:center;justify-content:center;gap:3px;height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#fff;color:#374151;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;transition:all .12s;white-space:nowrap}.osl-rg-page-btn svg{display:block}.osl-rg-page-btn:hover:not(:disabled){background:#f3f4f6;border-color:#9ca3af;box-shadow:0 1px 2px #0000000f}.osl-rg-page-btn:disabled{opacity:.35;cursor:not-allowed}.osl-rg-page-btn--num{min-width:30px;padding:0;font-size:12.5px}.osl-rg-page-btn--active{background:var(--osl-primary, #2563eb);border-color:var(--osl-primary, #2563eb);color:#fff;font-weight:700;box-shadow:0 2px 5px #2563eb59}.osl-rg-page-btn--active:hover:not(:disabled){background:#1d4ed8;border-color:#1d4ed8}.osl-rg-page-ellipsis{display:inline-flex;align-items:center;justify-content:center;width:26px;color:#9ca3af;font-size:13px;pointer-events:none}.osl-rg-page-size{height:30px;padding:0 8px;border:1px solid var(--osl-border-color, #e5e7eb);border-radius:6px;background:#fff;font-size:12px;font-family:inherit;color:#374151;cursor:pointer;outline:none;transition:border-color .15s}.osl-rg-page-size:focus{border-color:var(--osl-primary, #2563eb)}.osl-rg-col-menu{position:fixed;z-index:9999;min-width:210px;background:#fff;border:1px solid #e5e7eb;border-radius:11px;box-shadow:0 12px 36px #00000029,0 3px 10px #00000014;overflow:hidden;animation:rg-fade-up .13s cubic-bezier(.16,1,.3,1)}.osl-rg-col-menu__header{padding:9px 14px 7px;font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:#9ca3af;border-bottom:1px solid #f3f4f6;background:#fafbff}.osl-rg-col-menu__divider{height:1px;background:#f3f4f6;margin:2px 0}.osl-rg-col-menu__item{display:flex;align-items:center;gap:9px;width:100%;padding:9px 14px;background:transparent;border:none;border-left:3px solid transparent;border-bottom:none;text-align:left;font-size:13px;font-weight:500;font-family:inherit;color:#374151;cursor:pointer;transition:all .1s}.osl-rg-col-menu__item svg{color:#9ca3af;display:block}.osl-rg-col-menu__item:hover{background:#f0f4ff;color:var(--osl-primary, #2563eb);border-left-color:var(--osl-primary, #2563eb)}.osl-rg-col-menu__item:hover svg{color:var(--osl-primary, #2563eb)}.osl-rg-col-menu__item--danger:hover{background:#fff5f5;color:#dc2626;border-left-color:#dc2626}.osl-rg-col-menu__item--danger:hover svg{color:#dc2626}.osl-rg-config-backdrop{position:fixed;inset:0;z-index:9000;background:#0003;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.osl-rg-config-panel{position:fixed;top:0;right:0;bottom:0;z-index:9001;width:300px;background:#fff;border-left:1px solid #e5e7eb;box-shadow:-6px 0 24px #00000021;display:flex;flex-direction:column;animation:rg-slide-in .2s cubic-bezier(.16,1,.3,1)}.osl-rg-config-panel__header{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-bottom:1px solid #e5e7eb;background:linear-gradient(135deg,#f0f4ff,#e8efff);flex-shrink:0}.osl-rg-config-panel__title{display:flex;align-items:center;gap:8px;font-size:14px;font-weight:700;color:#1e3a6e}.osl-rg-config-panel__title svg{color:#3b82f6}.osl-rg-config-panel__close{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border:none;background:transparent;color:#9ca3af;cursor:pointer;border-radius:7px;padding:0;transition:all .12s}.osl-rg-config-panel__close svg{width:16px;height:16px;display:block}.osl-rg-config-panel__close:hover{background:#00000012;color:#374151}.osl-rg-config-panel__actions{display:flex;gap:8px;padding:10px 14px;border-bottom:1px solid #f3f4f6;flex-shrink:0;background:#fafbff}.osl-rg-config-hint{padding:6px 16px;font-size:10.5px;color:#b0b7c3;font-style:italic;border-bottom:1px solid #f3f4f6;flex-shrink:0}.osl-rg-config-action{display:inline-flex;align-items:center;gap:5px;height:30px;padding:0 10px;border:1px solid #e5e7eb;border-radius:6px;background:#fff;color:#374151;font-size:12px;font-family:inherit;font-weight:500;cursor:pointer;transition:all .12s}.osl-rg-config-action svg{display:block}.osl-rg-config-action:hover{background:#f3f4f6;border-color:#9ca3af}.osl-rg-config-list{flex:1;overflow-y:auto;padding:6px 0}.osl-rg-config-list::-webkit-scrollbar{width:5px}.osl-rg-config-list::-webkit-scrollbar-thumb{background:#e5e7eb;border-radius:4px}.osl-rg-config-list::-webkit-scrollbar-thumb:hover{background:#d1d5db}.osl-rg-config-item{display:flex;align-items:center;gap:9px;padding:7px 14px;transition:background .1s;cursor:grab}.osl-rg-config-item:hover{background:#f9fafb}.osl-rg-config-item:active{cursor:grabbing}.osl-rg-config-item--hidden{opacity:.5}.osl-rg-config-item__label{flex:1;font-size:13px;color:#374151;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}.osl-rg-config-item__label--hidden{color:#9ca3af;font-style:italic}.osl-rg-config-item__pin-btn{display:flex;align-items:center;justify-content:center;width:24px;height:24px;border:1px solid #e5e7eb;background:#fff;color:#d1d5db;cursor:pointer;padding:0;border-radius:5px;transition:all .12s;flex-shrink:0}.osl-rg-config-item__pin-btn svg{display:block}.osl-rg-config-item__pin-btn:hover,.osl-rg-config-item__pin-btn--active{background:#eff6ff;border-color:#93c5fd;color:var(--osl-primary, #2563eb)}.osl-rg-copy-toast{position:fixed;bottom:24px;left:50%;transform:translate(-50%);z-index:99999;display:flex;align-items:center;gap:7px;background:#1f2937;color:#fff;font-size:12.5px;font-weight:500;padding:8px 18px;border-radius:22px;animation:rg-fade-up .2s ease;box-shadow:0 6px 16px #0000004d;pointer-events:none;white-space:nowrap;font-family:inherit}.osl-rg-copy-toast svg{flex-shrink:0;color:#4ade80}\n"] }]
|
|
3563
|
+
}], propDecorators: { columns: [{
|
|
3564
|
+
type: Input
|
|
3565
|
+
}], datasource: [{
|
|
3566
|
+
type: Input
|
|
3567
|
+
}], loading: [{
|
|
3568
|
+
type: Input
|
|
3569
|
+
}], totalRecords: [{
|
|
3570
|
+
type: Input
|
|
3571
|
+
}], autoMode: [{
|
|
3572
|
+
type: Input
|
|
3573
|
+
}], isPaginated: [{
|
|
3574
|
+
type: Input
|
|
3575
|
+
}], pageSize: [{
|
|
3576
|
+
type: Input
|
|
3577
|
+
}], tableHeight: [{
|
|
3578
|
+
type: Input
|
|
3579
|
+
}], striped: [{
|
|
3580
|
+
type: Input
|
|
3581
|
+
}], exportable: [{
|
|
3582
|
+
type: Input
|
|
3583
|
+
}], rowHeight: [{
|
|
3584
|
+
type: Input
|
|
3585
|
+
}], rowSelection: [{
|
|
3586
|
+
type: Input
|
|
3587
|
+
}], showAggregates: [{
|
|
3588
|
+
type: Input
|
|
3589
|
+
}], title: [{
|
|
3590
|
+
type: Input
|
|
3591
|
+
}], pageChange: [{
|
|
3592
|
+
type: Output
|
|
3593
|
+
}], pageSizeChange: [{
|
|
3594
|
+
type: Output
|
|
3595
|
+
}], sortChange: [{
|
|
3596
|
+
type: Output
|
|
3597
|
+
}], rowClick: [{
|
|
3598
|
+
type: Output
|
|
3599
|
+
}], selectionChange: [{
|
|
3600
|
+
type: Output
|
|
3601
|
+
}], headerScrollRef: [{
|
|
3602
|
+
type: ViewChild,
|
|
3603
|
+
args: ['headerScrollRef']
|
|
3604
|
+
}], bodyScrollRef: [{
|
|
3605
|
+
type: ViewChild,
|
|
3606
|
+
args: ['bodyScrollRef']
|
|
3607
|
+
}], onMouseMove: [{
|
|
3608
|
+
type: HostListener,
|
|
3609
|
+
args: ['document:mousemove', ['$event']]
|
|
3610
|
+
}], onMouseUp: [{
|
|
3611
|
+
type: HostListener,
|
|
3612
|
+
args: ['document:mouseup']
|
|
3613
|
+
}], onDocumentClick: [{
|
|
3614
|
+
type: HostListener,
|
|
3615
|
+
args: ['document:click']
|
|
3616
|
+
}] } });
|
|
3617
|
+
|
|
3618
|
+
class OslReportForm {
|
|
3619
|
+
title = 'Report';
|
|
3620
|
+
formElements = [];
|
|
3621
|
+
_model = {};
|
|
3622
|
+
set model(val) { this._model = val ?? {}; }
|
|
3623
|
+
get model() { return this._model; }
|
|
3624
|
+
modelChange = new EventEmitter();
|
|
3625
|
+
generateOptions = ['pdf', 'grid', 'excel'];
|
|
3626
|
+
reportColumns = [];
|
|
3627
|
+
onGenerate;
|
|
3628
|
+
skeletonLoading = false;
|
|
3629
|
+
skeletonTheme = 'light';
|
|
3630
|
+
pageSize = 50;
|
|
3631
|
+
selectedType = 'grid';
|
|
3632
|
+
generating = false;
|
|
3633
|
+
resultDatasource = [];
|
|
3634
|
+
showResultView = false;
|
|
3635
|
+
ngOnInit() {
|
|
3636
|
+
if (this.generateOptions.length > 0 && !this.generateOptions.includes(this.selectedType)) {
|
|
3637
|
+
this.selectedType = this.generateOptions[0];
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
selectType(type) {
|
|
3641
|
+
this.selectedType = type;
|
|
3642
|
+
}
|
|
3643
|
+
async generate() {
|
|
3644
|
+
if (!this.onGenerate)
|
|
3645
|
+
return;
|
|
3646
|
+
this.generating = true;
|
|
3647
|
+
try {
|
|
3648
|
+
const result = await Promise.resolve(this.onGenerate({ ...this._model }, this.selectedType));
|
|
3649
|
+
this.resultDatasource = result || [];
|
|
3650
|
+
if (this.selectedType === 'grid') {
|
|
3651
|
+
this.showResultView = true;
|
|
3652
|
+
}
|
|
3653
|
+
else if (this.selectedType === 'excel') {
|
|
3654
|
+
this._exportCsv(this.resultDatasource);
|
|
3655
|
+
}
|
|
3656
|
+
else if (this.selectedType === 'pdf') {
|
|
3657
|
+
this._exportPdf(this.resultDatasource);
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
finally {
|
|
3661
|
+
this.generating = false;
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
backToForm() {
|
|
3665
|
+
this.showResultView = false;
|
|
3666
|
+
this.resultDatasource = [];
|
|
3667
|
+
}
|
|
3668
|
+
reExportExcel() { this._exportCsv(this.resultDatasource); }
|
|
3669
|
+
reExportPdf() { this._exportPdf(this.resultDatasource); }
|
|
3670
|
+
typeLabel(type) {
|
|
3671
|
+
return type === 'pdf' ? 'PDF' : type === 'excel' ? 'Excel' : 'Grid';
|
|
3672
|
+
}
|
|
3673
|
+
typeIcon(type) {
|
|
3674
|
+
return type === 'pdf' ? 'picture_as_pdf' : type === 'excel' ? 'table_chart' : 'grid_on';
|
|
3675
|
+
}
|
|
3676
|
+
onModelChange(val) {
|
|
3677
|
+
this._model = val;
|
|
3678
|
+
this.modelChange.emit(val);
|
|
3679
|
+
}
|
|
3680
|
+
_exportCsv(data) {
|
|
3681
|
+
if (!data.length || !this.reportColumns.length)
|
|
3682
|
+
return;
|
|
3683
|
+
const cols = this.reportColumns;
|
|
3684
|
+
const header = cols.map(c => `"${c.label}"`).join(',');
|
|
3685
|
+
const rows = data.map(row => cols.map(col => `"${String(row[col.key] ?? '').replace(/"/g, '""')}"`).join(','));
|
|
3686
|
+
const blob = new Blob([[header, ...rows].join('\n')], { type: 'text/csv;charset=utf-8;' });
|
|
3687
|
+
const a = Object.assign(document.createElement('a'), {
|
|
3688
|
+
href: URL.createObjectURL(blob),
|
|
3689
|
+
download: `${this.title || 'report'}.csv`,
|
|
3690
|
+
});
|
|
3691
|
+
a.click();
|
|
3692
|
+
URL.revokeObjectURL(a.href);
|
|
3693
|
+
}
|
|
3694
|
+
_exportPdf(data) {
|
|
3695
|
+
if (!data.length || !this.reportColumns.length)
|
|
3696
|
+
return;
|
|
3697
|
+
const cols = this.reportColumns;
|
|
3698
|
+
const headerRow = cols.map(c => `<th>${c.label}</th>`).join('');
|
|
3699
|
+
const bodyRows = data.map(row => `<tr>${cols.map(col => `<td>${String(row[col.key] ?? '')}</td>`).join('')}</tr>`).join('');
|
|
3700
|
+
const w = window.open('', '_blank');
|
|
3701
|
+
if (!w)
|
|
3702
|
+
return;
|
|
3703
|
+
w.document.write(`<!DOCTYPE html><html><head><title>${this.title}</title>
|
|
3704
|
+
<style>
|
|
3705
|
+
body{font-family:Arial,sans-serif;padding:24px;font-size:13px;color:#111827}
|
|
3706
|
+
h2{color:#1e3a8a;margin:0 0 4px;font-size:20px;font-weight:700}
|
|
3707
|
+
.subtitle{color:#6b7280;font-size:12px;margin-bottom:20px}
|
|
3708
|
+
table{border-collapse:collapse;width:100%}
|
|
3709
|
+
th{background:#1e3a8a;color:#fff;padding:9px 12px;text-align:left;font-weight:600;font-size:12px}
|
|
3710
|
+
td{padding:8px 12px;border-bottom:1px solid #e5e7eb;font-size:12px}
|
|
3711
|
+
tr:nth-child(even) td{background:#f8fafc}
|
|
3712
|
+
@media print{@page{margin:.8cm}}
|
|
3713
|
+
</style>
|
|
3714
|
+
</head><body>
|
|
3715
|
+
<h2>${this.title}</h2>
|
|
3716
|
+
<p class="subtitle">Generated on ${new Date().toLocaleString()}</p>
|
|
3717
|
+
<table><thead><tr>${headerRow}</tr></thead><tbody>${bodyRows}</tbody></table>
|
|
3718
|
+
<script>window.onload=function(){window.print();}<\/script>
|
|
3719
|
+
</body></html>`);
|
|
3720
|
+
w.document.close();
|
|
3721
|
+
}
|
|
3722
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslReportForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3723
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: OslReportForm, isStandalone: false, selector: "osl-report-form", inputs: { title: "title", formElements: "formElements", model: "model", generateOptions: "generateOptions", reportColumns: "reportColumns", onGenerate: "onGenerate", skeletonLoading: "skeletonLoading", skeletonTheme: "skeletonTheme", pageSize: "pageSize" }, outputs: { modelChange: "modelChange" }, ngImport: i0, template: "<!-- \u2500\u2500 Form view (hidden when results are shown) \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 (!showResultView) {\n <div class=\"orf-card\">\n\n <!-- Header -->\n <div class=\"orf-header\">\n <div class=\"orf-header__left\">\n <div class=\"orf-header__icon-wrap\">\n <mat-icon class=\"orf-header__icon\">assessment</mat-icon>\n </div>\n <div class=\"orf-header__text\">\n <h4 class=\"orf-header__title\">{{ title }}</h4>\n <p class=\"orf-header__sub\">Configure parameters and generate report</p>\n </div>\n </div>\n\n <div class=\"orf-header__right\">\n @for (type of generateOptions; track type) {\n <button\n class=\"orf-type-btn\"\n [class.active]=\"selectedType === type\"\n [class.orf-type-btn--pdf]=\"type === 'pdf'\"\n [class.orf-type-btn--excel]=\"type === 'excel'\"\n [class.orf-type-btn--grid]=\"type === 'grid'\"\n (click)=\"selectType(type)\"\n type=\"button\"\n >\n <mat-icon>{{ typeIcon(type) }}</mat-icon>\n <span>{{ typeLabel(type) }}</span>\n </button>\n }\n </div>\n </div>\n\n <!-- Divider -->\n <div class=\"orf-divider\"></div>\n\n <!-- Form Body -->\n <div class=\"orf-body\">\n <osl-dynamic-form\n [elements]=\"formElements\"\n [model]=\"model\"\n (modelChange)=\"onModelChange($event)\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n ></osl-dynamic-form>\n </div>\n\n <!-- Footer -->\n <div class=\"orf-footer\">\n <button\n class=\"orf-generate-btn\"\n (click)=\"generate()\"\n [disabled]=\"generating\"\n type=\"button\"\n >\n @if (generating) {\n <span class=\"orf-generate-btn__spinner\"></span>\n <span>Generating...</span>\n } @else {\n <mat-icon>{{ typeIcon(selectedType) }}</mat-icon>\n <span>Generate {{ typeLabel(selectedType) }}</span>\n <mat-icon class=\"orf-generate-btn__arrow\">arrow_forward</mat-icon>\n }\n </button>\n </div>\n\n </div>\n}\n\n<!-- \u2500\u2500 Full-screen results 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 (showResultView) {\n <div class=\"orf-results-view\">\n\n <!-- Results header bar -->\n <div class=\"orf-results-header\">\n\n <div class=\"orf-results-header__left\">\n <button class=\"orf-back-btn\" (click)=\"backToForm()\" type=\"button\">\n <mat-icon>arrow_back</mat-icon>\n <span>Back</span>\n </button>\n <div class=\"orf-results-header__sep\"></div>\n <mat-icon class=\"orf-results-header__report-icon\">assessment</mat-icon>\n <span class=\"orf-results-header__title\">{{ title }}</span>\n <span class=\"orf-results-badge\">\n <mat-icon>table_rows</mat-icon>\n {{ resultDatasource.length | number }} records\n </span>\n </div>\n\n <div class=\"orf-results-header__right\">\n @if (generateOptions.includes('excel')) {\n <button class=\"orf-reexport-btn orf-reexport-btn--excel\" (click)=\"reExportExcel()\" type=\"button\">\n <mat-icon>table_chart</mat-icon>\n <span>Excel</span>\n </button>\n }\n @if (generateOptions.includes('pdf')) {\n <button class=\"orf-reexport-btn orf-reexport-btn--pdf\" (click)=\"reExportPdf()\" type=\"button\">\n <mat-icon>picture_as_pdf</mat-icon>\n <span>PDF</span>\n </button>\n }\n </div>\n\n </div>\n\n <!-- Report grid -->\n <div class=\"orf-results-body\">\n <osl-report-grid\n [columns]=\"reportColumns\"\n [datasource]=\"resultDatasource\"\n [loading]=\"generating\"\n [title]=\"title\"\n tableHeight=\"calc(100vh - 200px)\"\n [pageSize]=\"pageSize\"\n [exportable]=\"true\"\n [showAggregates]=\"true\"\n [striped]=\"true\"\n ></osl-report-grid>\n </div>\n\n </div>\n}\n", styles: ["@keyframes orf-fade-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes orf-slide-in{0%{opacity:0;transform:translate(-10px)}to{opacity:1;transform:translate(0)}}@keyframes orf-spin{to{transform:rotate(360deg)}}.orf-card{background:#fff;border-radius:12px;border:1px solid #e5e7eb;box-shadow:0 4px 20px #00000014,0 1px 4px #0000000a;overflow:hidden}.orf-header{background:linear-gradient(135deg,#1e3a8a,#2563eb 55%,#60a5fa);padding:22px 24px;display:flex;align-items:center;justify-content:space-between;gap:16px;position:relative;overflow:hidden;flex-wrap:wrap}.orf-header:before{content:\"\";position:absolute;width:260px;height:260px;border-radius:50%;background:#ffffff0d;top:-110px;right:10px;pointer-events:none}.orf-header:after{content:\"\";position:absolute;width:160px;height:160px;border-radius:50%;background:#ffffff12;bottom:-70px;right:200px;pointer-events:none}.orf-header__left{display:flex;align-items:center;gap:14px;position:relative;z-index:1}.orf-header__icon-wrap{width:50px;height:50px;border-radius:14px;background:#ffffff26;border:1px solid rgba(255,255,255,.22);display:flex;align-items:center;justify-content:center;flex-shrink:0;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.orf-header__icon{color:#fff;font-size:26px;width:26px;height:26px}.orf-header__text{display:flex;flex-direction:column;gap:3px}.orf-header__title{margin:0;font-size:19px;font-weight:700;color:#fff;letter-spacing:-.015em;line-height:1.2}.orf-header__sub{margin:0;font-size:12px;color:#ffffffad}.orf-header__right{display:flex;align-items:center;gap:8px;position:relative;z-index:1;flex-wrap:wrap}.orf-type-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border-radius:8px;border:1px solid rgba(255,255,255,.28);background:#ffffff1f;color:#ffffffd1;font-size:13px;font-weight:500;cursor:pointer;transition:all .2s ease;white-space:nowrap;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit}.orf-type-btn mat-icon{font-size:16px;width:16px;height:16px}.orf-type-btn:hover{background:#ffffff38;border-color:#ffffff73;color:#fff;transform:translateY(-1px)}.orf-type-btn.active{background:#fffffff2;border-color:transparent;color:#1e3a8a;box-shadow:0 4px 14px #00000038;transform:translateY(-1px);font-weight:600}.orf-type-btn.active mat-icon{color:#1e3a8a}.orf-type-btn--pdf.active{color:#dc2626}.orf-type-btn--pdf.active mat-icon{color:#dc2626}.orf-type-btn--excel.active{color:#16a34a}.orf-type-btn--excel.active mat-icon{color:#16a34a}.orf-type-btn--grid.active{color:#2563eb}.orf-type-btn--grid.active mat-icon{color:#2563eb}.orf-divider{height:1px;background:linear-gradient(90deg,#e5e7eb,#c7d2fe,#e5e7eb)}.orf-body{padding:20px 20px 4px}.orf-footer{padding:12px 20px 20px;display:flex;justify-content:flex-end}.orf-generate-btn{display:inline-flex;align-items:center;gap:8px;padding:10px 22px;border-radius:8px;border:none;background:linear-gradient(135deg,#1e3a8a,#2563eb);color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .25s ease;box-shadow:0 4px 16px #2563eb52;letter-spacing:.01em;font-family:inherit}.orf-generate-btn mat-icon{font-size:18px;width:18px;height:18px}.orf-generate-btn__arrow{transition:transform .2s ease;font-size:16px!important;width:16px!important;height:16px!important;margin-left:2px}.orf-generate-btn:hover:not([disabled]){background:linear-gradient(135deg,#1e40af,#1d4ed8);box-shadow:0 6px 22px #2563eb7a;transform:translateY(-1px)}.orf-generate-btn:hover:not([disabled]) .orf-generate-btn__arrow{transform:translate(4px)}.orf-generate-btn:active:not([disabled]){transform:translateY(0);box-shadow:0 2px 8px #2563eb38}.orf-generate-btn[disabled]{opacity:.72;cursor:not-allowed;box-shadow:none}.orf-generate-btn__spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;display:inline-block;animation:orf-spin .7s linear infinite;flex-shrink:0}.orf-results-view{position:fixed;inset:0;z-index:200;display:flex;flex-direction:column;background:#f1f5f9;animation:orf-fade-in .25s ease}.orf-results-header{flex-shrink:0;height:56px;background:linear-gradient(135deg,#1e3a8a,#2563eb 60%,#3b82f6);display:flex;align-items:center;justify-content:space-between;padding:0 16px;gap:12px;position:relative;overflow:hidden;box-shadow:0 2px 12px #2563eb66}.orf-results-header:after{content:\"\";position:absolute;width:180px;height:180px;border-radius:50%;background:#ffffff0f;top:-80px;right:60px;pointer-events:none}.orf-results-header__left{display:flex;align-items:center;gap:10px;position:relative;z-index:1;min-width:0;flex:1}.orf-results-header__sep{width:1px;height:22px;background:#ffffff40;flex-shrink:0}.orf-results-header__report-icon{color:#ffffffd9;font-size:20px;width:20px;height:20px;flex-shrink:0}.orf-results-header__title{font-size:15px;font-weight:700;color:#fff;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.orf-results-badge{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;background:#ffffff26;border:1px solid rgba(255,255,255,.2);color:#ffffffe6;font-size:11px;font-weight:600;white-space:nowrap;flex-shrink:0;animation:orf-slide-in .3s ease}.orf-results-badge mat-icon{font-size:13px;width:13px;height:13px}.orf-back-btn{display:inline-flex;align-items:center;gap:5px;padding:6px 14px;border-radius:7px;border:1px solid rgba(255,255,255,.3);background:#ffffff24;color:#fff;font-size:13px;font-weight:500;cursor:pointer;transition:all .18s ease;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit;flex-shrink:0}.orf-back-btn mat-icon{font-size:17px;width:17px;height:17px;transition:transform .18s ease}.orf-back-btn:hover{background:#ffffff3d;border-color:#ffffff80}.orf-back-btn:hover mat-icon{transform:translate(-3px)}.orf-back-btn:active{transform:scale(.97)}.orf-results-header__right{display:flex;align-items:center;gap:8px;position:relative;z-index:1;flex-shrink:0}.orf-reexport-btn{display:inline-flex;align-items:center;gap:5px;padding:6px 13px;border-radius:7px;border:1px solid rgba(255,255,255,.28);background:#ffffff1f;color:#ffffffd9;font-size:12px;font-weight:500;cursor:pointer;transition:all .18s ease;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit;white-space:nowrap}.orf-reexport-btn mat-icon{font-size:15px;width:15px;height:15px}.orf-reexport-btn:hover{background:#ffffff38;border-color:#ffffff73;color:#fff;transform:translateY(-1px)}.orf-reexport-btn--excel:hover{background:#16a34a59;border-color:#16a34a80}.orf-reexport-btn--pdf:hover{background:#dc262659;border-color:#dc262680}.orf-results-body{flex:1;overflow:hidden;padding:12px 12px 0;display:flex;flex-direction:column}.orf-results-body osl-report-grid{flex:1;display:block}\n"], dependencies: [{ 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: OslReportGrid, selector: "osl-report-grid", inputs: ["columns", "datasource", "loading", "totalRecords", "autoMode", "isPaginated", "pageSize", "tableHeight", "striped", "exportable", "rowHeight", "rowSelection", "showAggregates", "title"], outputs: ["pageChange", "pageSizeChange", "sortChange", "rowClick", "selectionChange"] }, { kind: "pipe", type: i1$2.DecimalPipe, name: "number" }] });
|
|
3724
|
+
}
|
|
3725
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: OslReportForm, decorators: [{
|
|
3726
|
+
type: Component,
|
|
3727
|
+
args: [{ selector: 'osl-report-form', standalone: false, template: "<!-- \u2500\u2500 Form view (hidden when results are shown) \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 (!showResultView) {\n <div class=\"orf-card\">\n\n <!-- Header -->\n <div class=\"orf-header\">\n <div class=\"orf-header__left\">\n <div class=\"orf-header__icon-wrap\">\n <mat-icon class=\"orf-header__icon\">assessment</mat-icon>\n </div>\n <div class=\"orf-header__text\">\n <h4 class=\"orf-header__title\">{{ title }}</h4>\n <p class=\"orf-header__sub\">Configure parameters and generate report</p>\n </div>\n </div>\n\n <div class=\"orf-header__right\">\n @for (type of generateOptions; track type) {\n <button\n class=\"orf-type-btn\"\n [class.active]=\"selectedType === type\"\n [class.orf-type-btn--pdf]=\"type === 'pdf'\"\n [class.orf-type-btn--excel]=\"type === 'excel'\"\n [class.orf-type-btn--grid]=\"type === 'grid'\"\n (click)=\"selectType(type)\"\n type=\"button\"\n >\n <mat-icon>{{ typeIcon(type) }}</mat-icon>\n <span>{{ typeLabel(type) }}</span>\n </button>\n }\n </div>\n </div>\n\n <!-- Divider -->\n <div class=\"orf-divider\"></div>\n\n <!-- Form Body -->\n <div class=\"orf-body\">\n <osl-dynamic-form\n [elements]=\"formElements\"\n [model]=\"model\"\n (modelChange)=\"onModelChange($event)\"\n [skeletonLoading]=\"skeletonLoading\"\n [skeletonTheme]=\"skeletonTheme\"\n ></osl-dynamic-form>\n </div>\n\n <!-- Footer -->\n <div class=\"orf-footer\">\n <button\n class=\"orf-generate-btn\"\n (click)=\"generate()\"\n [disabled]=\"generating\"\n type=\"button\"\n >\n @if (generating) {\n <span class=\"orf-generate-btn__spinner\"></span>\n <span>Generating...</span>\n } @else {\n <mat-icon>{{ typeIcon(selectedType) }}</mat-icon>\n <span>Generate {{ typeLabel(selectedType) }}</span>\n <mat-icon class=\"orf-generate-btn__arrow\">arrow_forward</mat-icon>\n }\n </button>\n </div>\n\n </div>\n}\n\n<!-- \u2500\u2500 Full-screen results 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 (showResultView) {\n <div class=\"orf-results-view\">\n\n <!-- Results header bar -->\n <div class=\"orf-results-header\">\n\n <div class=\"orf-results-header__left\">\n <button class=\"orf-back-btn\" (click)=\"backToForm()\" type=\"button\">\n <mat-icon>arrow_back</mat-icon>\n <span>Back</span>\n </button>\n <div class=\"orf-results-header__sep\"></div>\n <mat-icon class=\"orf-results-header__report-icon\">assessment</mat-icon>\n <span class=\"orf-results-header__title\">{{ title }}</span>\n <span class=\"orf-results-badge\">\n <mat-icon>table_rows</mat-icon>\n {{ resultDatasource.length | number }} records\n </span>\n </div>\n\n <div class=\"orf-results-header__right\">\n @if (generateOptions.includes('excel')) {\n <button class=\"orf-reexport-btn orf-reexport-btn--excel\" (click)=\"reExportExcel()\" type=\"button\">\n <mat-icon>table_chart</mat-icon>\n <span>Excel</span>\n </button>\n }\n @if (generateOptions.includes('pdf')) {\n <button class=\"orf-reexport-btn orf-reexport-btn--pdf\" (click)=\"reExportPdf()\" type=\"button\">\n <mat-icon>picture_as_pdf</mat-icon>\n <span>PDF</span>\n </button>\n }\n </div>\n\n </div>\n\n <!-- Report grid -->\n <div class=\"orf-results-body\">\n <osl-report-grid\n [columns]=\"reportColumns\"\n [datasource]=\"resultDatasource\"\n [loading]=\"generating\"\n [title]=\"title\"\n tableHeight=\"calc(100vh - 200px)\"\n [pageSize]=\"pageSize\"\n [exportable]=\"true\"\n [showAggregates]=\"true\"\n [striped]=\"true\"\n ></osl-report-grid>\n </div>\n\n </div>\n}\n", styles: ["@keyframes orf-fade-in{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes orf-slide-in{0%{opacity:0;transform:translate(-10px)}to{opacity:1;transform:translate(0)}}@keyframes orf-spin{to{transform:rotate(360deg)}}.orf-card{background:#fff;border-radius:12px;border:1px solid #e5e7eb;box-shadow:0 4px 20px #00000014,0 1px 4px #0000000a;overflow:hidden}.orf-header{background:linear-gradient(135deg,#1e3a8a,#2563eb 55%,#60a5fa);padding:22px 24px;display:flex;align-items:center;justify-content:space-between;gap:16px;position:relative;overflow:hidden;flex-wrap:wrap}.orf-header:before{content:\"\";position:absolute;width:260px;height:260px;border-radius:50%;background:#ffffff0d;top:-110px;right:10px;pointer-events:none}.orf-header:after{content:\"\";position:absolute;width:160px;height:160px;border-radius:50%;background:#ffffff12;bottom:-70px;right:200px;pointer-events:none}.orf-header__left{display:flex;align-items:center;gap:14px;position:relative;z-index:1}.orf-header__icon-wrap{width:50px;height:50px;border-radius:14px;background:#ffffff26;border:1px solid rgba(255,255,255,.22);display:flex;align-items:center;justify-content:center;flex-shrink:0;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.orf-header__icon{color:#fff;font-size:26px;width:26px;height:26px}.orf-header__text{display:flex;flex-direction:column;gap:3px}.orf-header__title{margin:0;font-size:19px;font-weight:700;color:#fff;letter-spacing:-.015em;line-height:1.2}.orf-header__sub{margin:0;font-size:12px;color:#ffffffad}.orf-header__right{display:flex;align-items:center;gap:8px;position:relative;z-index:1;flex-wrap:wrap}.orf-type-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;border-radius:8px;border:1px solid rgba(255,255,255,.28);background:#ffffff1f;color:#ffffffd1;font-size:13px;font-weight:500;cursor:pointer;transition:all .2s ease;white-space:nowrap;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit}.orf-type-btn mat-icon{font-size:16px;width:16px;height:16px}.orf-type-btn:hover{background:#ffffff38;border-color:#ffffff73;color:#fff;transform:translateY(-1px)}.orf-type-btn.active{background:#fffffff2;border-color:transparent;color:#1e3a8a;box-shadow:0 4px 14px #00000038;transform:translateY(-1px);font-weight:600}.orf-type-btn.active mat-icon{color:#1e3a8a}.orf-type-btn--pdf.active{color:#dc2626}.orf-type-btn--pdf.active mat-icon{color:#dc2626}.orf-type-btn--excel.active{color:#16a34a}.orf-type-btn--excel.active mat-icon{color:#16a34a}.orf-type-btn--grid.active{color:#2563eb}.orf-type-btn--grid.active mat-icon{color:#2563eb}.orf-divider{height:1px;background:linear-gradient(90deg,#e5e7eb,#c7d2fe,#e5e7eb)}.orf-body{padding:20px 20px 4px}.orf-footer{padding:12px 20px 20px;display:flex;justify-content:flex-end}.orf-generate-btn{display:inline-flex;align-items:center;gap:8px;padding:10px 22px;border-radius:8px;border:none;background:linear-gradient(135deg,#1e3a8a,#2563eb);color:#fff;font-size:14px;font-weight:600;cursor:pointer;transition:all .25s ease;box-shadow:0 4px 16px #2563eb52;letter-spacing:.01em;font-family:inherit}.orf-generate-btn mat-icon{font-size:18px;width:18px;height:18px}.orf-generate-btn__arrow{transition:transform .2s ease;font-size:16px!important;width:16px!important;height:16px!important;margin-left:2px}.orf-generate-btn:hover:not([disabled]){background:linear-gradient(135deg,#1e40af,#1d4ed8);box-shadow:0 6px 22px #2563eb7a;transform:translateY(-1px)}.orf-generate-btn:hover:not([disabled]) .orf-generate-btn__arrow{transform:translate(4px)}.orf-generate-btn:active:not([disabled]){transform:translateY(0);box-shadow:0 2px 8px #2563eb38}.orf-generate-btn[disabled]{opacity:.72;cursor:not-allowed;box-shadow:none}.orf-generate-btn__spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;display:inline-block;animation:orf-spin .7s linear infinite;flex-shrink:0}.orf-results-view{position:fixed;inset:0;z-index:200;display:flex;flex-direction:column;background:#f1f5f9;animation:orf-fade-in .25s ease}.orf-results-header{flex-shrink:0;height:56px;background:linear-gradient(135deg,#1e3a8a,#2563eb 60%,#3b82f6);display:flex;align-items:center;justify-content:space-between;padding:0 16px;gap:12px;position:relative;overflow:hidden;box-shadow:0 2px 12px #2563eb66}.orf-results-header:after{content:\"\";position:absolute;width:180px;height:180px;border-radius:50%;background:#ffffff0f;top:-80px;right:60px;pointer-events:none}.orf-results-header__left{display:flex;align-items:center;gap:10px;position:relative;z-index:1;min-width:0;flex:1}.orf-results-header__sep{width:1px;height:22px;background:#ffffff40;flex-shrink:0}.orf-results-header__report-icon{color:#ffffffd9;font-size:20px;width:20px;height:20px;flex-shrink:0}.orf-results-header__title{font-size:15px;font-weight:700;color:#fff;letter-spacing:-.01em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.orf-results-badge{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;border-radius:20px;background:#ffffff26;border:1px solid rgba(255,255,255,.2);color:#ffffffe6;font-size:11px;font-weight:600;white-space:nowrap;flex-shrink:0;animation:orf-slide-in .3s ease}.orf-results-badge mat-icon{font-size:13px;width:13px;height:13px}.orf-back-btn{display:inline-flex;align-items:center;gap:5px;padding:6px 14px;border-radius:7px;border:1px solid rgba(255,255,255,.3);background:#ffffff24;color:#fff;font-size:13px;font-weight:500;cursor:pointer;transition:all .18s ease;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit;flex-shrink:0}.orf-back-btn mat-icon{font-size:17px;width:17px;height:17px;transition:transform .18s ease}.orf-back-btn:hover{background:#ffffff3d;border-color:#ffffff80}.orf-back-btn:hover mat-icon{transform:translate(-3px)}.orf-back-btn:active{transform:scale(.97)}.orf-results-header__right{display:flex;align-items:center;gap:8px;position:relative;z-index:1;flex-shrink:0}.orf-reexport-btn{display:inline-flex;align-items:center;gap:5px;padding:6px 13px;border-radius:7px;border:1px solid rgba(255,255,255,.28);background:#ffffff1f;color:#ffffffd9;font-size:12px;font-weight:500;cursor:pointer;transition:all .18s ease;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);font-family:inherit;white-space:nowrap}.orf-reexport-btn mat-icon{font-size:15px;width:15px;height:15px}.orf-reexport-btn:hover{background:#ffffff38;border-color:#ffffff73;color:#fff;transform:translateY(-1px)}.orf-reexport-btn--excel:hover{background:#16a34a59;border-color:#16a34a80}.orf-reexport-btn--pdf:hover{background:#dc262659;border-color:#dc262680}.orf-results-body{flex:1;overflow:hidden;padding:12px 12px 0;display:flex;flex-direction:column}.orf-results-body osl-report-grid{flex:1;display:block}\n"] }]
|
|
3728
|
+
}], propDecorators: { title: [{
|
|
3729
|
+
type: Input
|
|
3730
|
+
}], formElements: [{
|
|
3731
|
+
type: Input
|
|
3732
|
+
}], model: [{
|
|
3733
|
+
type: Input
|
|
3734
|
+
}], modelChange: [{
|
|
3735
|
+
type: Output
|
|
3736
|
+
}], generateOptions: [{
|
|
3737
|
+
type: Input
|
|
3738
|
+
}], reportColumns: [{
|
|
3739
|
+
type: Input
|
|
3740
|
+
}], onGenerate: [{
|
|
3741
|
+
type: Input
|
|
3742
|
+
}], skeletonLoading: [{
|
|
3743
|
+
type: Input
|
|
3744
|
+
}], skeletonTheme: [{
|
|
3745
|
+
type: Input
|
|
3746
|
+
}], pageSize: [{
|
|
3747
|
+
type: Input
|
|
3748
|
+
}] } });
|
|
3749
|
+
|
|
2925
3750
|
class FormStructureModule {
|
|
2926
3751
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
2927
3752
|
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, declarations: [DynamicForm,
|
|
@@ -2939,9 +3764,14 @@ class FormStructureModule {
|
|
|
2939
3764
|
OslSearchbar,
|
|
2940
3765
|
OslGrid,
|
|
2941
3766
|
OslFormGrid,
|
|
2942
|
-
OslAutocompleteLister
|
|
3767
|
+
OslAutocompleteLister,
|
|
3768
|
+
OslReportGrid,
|
|
3769
|
+
OslReportForm], imports: [NgTemplateOutlet,
|
|
2943
3770
|
NgStyle,
|
|
3771
|
+
NgClass,
|
|
2944
3772
|
DatePipe,
|
|
3773
|
+
DecimalPipe,
|
|
3774
|
+
UpperCasePipe,
|
|
2945
3775
|
FormsModule,
|
|
2946
3776
|
ReactiveFormsModule,
|
|
2947
3777
|
MatFormFieldModule,
|
|
@@ -2952,7 +3782,9 @@ class FormStructureModule {
|
|
|
2952
3782
|
MatButtonModule,
|
|
2953
3783
|
OslSkeletonModule,
|
|
2954
3784
|
MatDatepickerModule,
|
|
2955
|
-
MatMenuModule
|
|
3785
|
+
MatMenuModule,
|
|
3786
|
+
ScrollingModule,
|
|
3787
|
+
DragDropModule], exports: [DynamicForm, OslSetup, OslGrid, OslFormGrid, Oslinput,
|
|
2956
3788
|
Osltextarea,
|
|
2957
3789
|
OslSelect,
|
|
2958
3790
|
OslRadio,
|
|
@@ -2963,7 +3795,7 @@ class FormStructureModule {
|
|
|
2963
3795
|
OslCheckbox,
|
|
2964
3796
|
OslButton,
|
|
2965
3797
|
OslSetup,
|
|
2966
|
-
OslSearchbar, OslAutocompleteLister] });
|
|
3798
|
+
OslSearchbar, OslAutocompleteLister, OslReportGrid, OslReportForm] });
|
|
2967
3799
|
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, providers: [
|
|
2968
3800
|
{ provide: AUTOCOMPLETE_LISTER_COMPONENT, useValue: OslAutocompleteLister },
|
|
2969
3801
|
], imports: [FormsModule,
|
|
@@ -2975,7 +3807,9 @@ class FormStructureModule {
|
|
|
2975
3807
|
MatButtonModule,
|
|
2976
3808
|
OslSkeletonModule,
|
|
2977
3809
|
MatDatepickerModule,
|
|
2978
|
-
MatMenuModule
|
|
3810
|
+
MatMenuModule,
|
|
3811
|
+
ScrollingModule,
|
|
3812
|
+
DragDropModule] });
|
|
2979
3813
|
}
|
|
2980
3814
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FormStructureModule, decorators: [{
|
|
2981
3815
|
type: NgModule,
|
|
@@ -2996,12 +3830,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2996
3830
|
OslSearchbar,
|
|
2997
3831
|
OslGrid,
|
|
2998
3832
|
OslFormGrid,
|
|
2999
|
-
OslAutocompleteLister
|
|
3833
|
+
OslAutocompleteLister,
|
|
3834
|
+
OslReportGrid,
|
|
3835
|
+
OslReportForm,
|
|
3000
3836
|
],
|
|
3001
3837
|
imports: [
|
|
3002
3838
|
NgTemplateOutlet,
|
|
3003
3839
|
NgStyle,
|
|
3840
|
+
NgClass,
|
|
3004
3841
|
DatePipe,
|
|
3842
|
+
DecimalPipe,
|
|
3843
|
+
UpperCasePipe,
|
|
3005
3844
|
FormsModule,
|
|
3006
3845
|
ReactiveFormsModule,
|
|
3007
3846
|
MatFormFieldModule,
|
|
@@ -3012,7 +3851,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3012
3851
|
MatButtonModule,
|
|
3013
3852
|
OslSkeletonModule,
|
|
3014
3853
|
MatDatepickerModule,
|
|
3015
|
-
MatMenuModule
|
|
3854
|
+
MatMenuModule,
|
|
3855
|
+
ScrollingModule,
|
|
3856
|
+
DragDropModule,
|
|
3016
3857
|
],
|
|
3017
3858
|
exports: [DynamicForm, OslSetup, OslGrid, OslFormGrid, Oslinput,
|
|
3018
3859
|
Osltextarea,
|
|
@@ -3025,7 +3866,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3025
3866
|
OslCheckbox,
|
|
3026
3867
|
OslButton,
|
|
3027
3868
|
OslSetup,
|
|
3028
|
-
OslSearchbar, OslAutocompleteLister],
|
|
3869
|
+
OslSearchbar, OslAutocompleteLister, OslReportGrid, OslReportForm],
|
|
3029
3870
|
providers: [
|
|
3030
3871
|
{ provide: AUTOCOMPLETE_LISTER_COMPONENT, useValue: OslAutocompleteLister },
|
|
3031
3872
|
],
|
|
@@ -4585,5 +5426,5 @@ var validation_util = /*#__PURE__*/Object.freeze({
|
|
|
4585
5426
|
* Generated bundle index. Do not edit.
|
|
4586
5427
|
*/
|
|
4587
5428
|
|
|
4588
|
-
export { array_util as ArrayUtil, date_util as DateUtil, DeleteConfirmation, DeleteConfirmationData, Dialog, DialogWrapper, DynamicForm, FormStructureModule, Httpbase, number_util as NumberUtil, object_util as ObjectUtil, OslAutocomplete, OslAutocompleteLister, OslBaseExtended, OslButton, OslCheckbox, OslDatepicker, OslFileUpload, OslFormGrid, OslGrid, OslRadio, OslSearchbar, OslSelect, OslSetup, OslSkeletonDirective, OslSkeletonModule, OslSkeletonThemeService, OslSlideToggle, Oslinput, Osltextarea, storage_util as StorageUtil, string_util as StringUtil, validation_util as ValidationUtil, baseComponent };
|
|
5429
|
+
export { array_util as ArrayUtil, date_util as DateUtil, DeleteConfirmation, DeleteConfirmationData, Dialog, DialogWrapper, DynamicForm, FormStructureModule, Httpbase, number_util as NumberUtil, object_util as ObjectUtil, OslAutocomplete, OslAutocompleteLister, OslBaseExtended, OslButton, OslCheckbox, OslDatepicker, OslFileUpload, OslFormGrid, OslGrid, OslRadio, OslReportForm, OslReportGrid, OslSearchbar, OslSelect, OslSetup, OslSkeletonDirective, OslSkeletonModule, OslSkeletonThemeService, OslSlideToggle, Oslinput, Osltextarea, storage_util as StorageUtil, string_util as StringUtil, validation_util as ValidationUtil, baseComponent };
|
|
4589
5430
|
//# sourceMappingURL=osl-base-extended.mjs.map
|