cloud-ide-element 1.1.19 → 1.1.21
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.
|
@@ -735,22 +735,19 @@ class CideInputComponent {
|
|
|
735
735
|
}
|
|
736
736
|
}
|
|
737
737
|
else if (type === 'datetime-local' && typeof value === 'string' && value) {
|
|
738
|
-
//
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
// Try to parse and format unformatted datetime to YYYY-MM-DDTHH:mm
|
|
745
|
-
const parsedDate = new Date(value);
|
|
746
|
-
if (!isNaN(parsedDate.getTime())) {
|
|
747
|
-
const year = parsedDate.getFullYear();
|
|
748
|
-
const month = String(parsedDate.getMonth() + 1).padStart(2, '0');
|
|
749
|
-
const day = String(parsedDate.getDate()).padStart(2, '0');
|
|
750
|
-
const hours = String(parsedDate.getHours()).padStart(2, '0');
|
|
751
|
-
const minutes = String(parsedDate.getMinutes()).padStart(2, '0');
|
|
752
|
-
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
738
|
+
// Try to parse and format datetime to YYYY-MM-DDTHH:mm
|
|
739
|
+
const parsedDate = new Date(value);
|
|
740
|
+
if (!isNaN(parsedDate.getTime())) {
|
|
741
|
+
// Check if already in correct format YYYY-MM-DDTHH:mm
|
|
742
|
+
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) {
|
|
743
|
+
return value; // Already in correct format
|
|
753
744
|
}
|
|
745
|
+
const year = parsedDate.getFullYear();
|
|
746
|
+
const month = String(parsedDate.getMonth() + 1).padStart(2, '0');
|
|
747
|
+
const day = String(parsedDate.getDate()).padStart(2, '0');
|
|
748
|
+
const hours = String(parsedDate.getHours()).padStart(2, '0');
|
|
749
|
+
const minutes = String(parsedDate.getMinutes()).padStart(2, '0');
|
|
750
|
+
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
754
751
|
}
|
|
755
752
|
}
|
|
756
753
|
else if (type === 'url' && typeof value === 'string' && value) {
|
|
@@ -844,9 +841,8 @@ class CideInputComponent {
|
|
|
844
841
|
if (typeof (value) == 'string') {
|
|
845
842
|
if (value?.length > 0) {
|
|
846
843
|
console.log(value, "value, datetime-local");
|
|
847
|
-
//
|
|
848
|
-
|
|
849
|
-
return datetimeRegex.test(value) && !isNaN(Date.parse(value));
|
|
844
|
+
// Just check if it can be parsed as a valid date (no regex validation)
|
|
845
|
+
return !isNaN(Date.parse(value));
|
|
850
846
|
}
|
|
851
847
|
else {
|
|
852
848
|
return false;
|
|
@@ -964,34 +960,27 @@ class CideInputComponent {
|
|
|
964
960
|
else if (this.type == 'datetime-local') {
|
|
965
961
|
if (typeof (value) == 'string' && value) {
|
|
966
962
|
console.log(value, "value, datetime-local, isControlValid");
|
|
967
|
-
// Validate datetime
|
|
968
|
-
const
|
|
969
|
-
if (
|
|
963
|
+
// Validate datetime by parsing (no regex validation)
|
|
964
|
+
const parsedDate = Date.parse(value);
|
|
965
|
+
if (isNaN(parsedDate)) {
|
|
970
966
|
validation_status.status = true;
|
|
971
|
-
validation_status.validation.required = `Invalid datetime
|
|
967
|
+
validation_status.validation.required = `Invalid datetime!`;
|
|
972
968
|
}
|
|
973
969
|
else {
|
|
974
|
-
|
|
975
|
-
if (
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
// Validate min datetime
|
|
981
|
-
if (this.min && typeof this.min === 'string') {
|
|
982
|
-
const minDate = Date.parse(this.min);
|
|
983
|
-
if (!isNaN(minDate) && parsedDate < minDate) {
|
|
984
|
-
validation_status.status = true;
|
|
985
|
-
validation_status.validation.required = `Datetime must be after ${this.min}!`;
|
|
986
|
-
}
|
|
970
|
+
// Validate min datetime
|
|
971
|
+
if (this.min && typeof this.min === 'string') {
|
|
972
|
+
const minDate = Date.parse(this.min);
|
|
973
|
+
if (!isNaN(minDate) && parsedDate < minDate) {
|
|
974
|
+
validation_status.status = true;
|
|
975
|
+
validation_status.validation.required = `Datetime must be after ${this.min}!`;
|
|
987
976
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
}
|
|
977
|
+
}
|
|
978
|
+
// Validate max datetime
|
|
979
|
+
if (this.max && typeof this.max === 'string') {
|
|
980
|
+
const maxDate = Date.parse(this.max);
|
|
981
|
+
if (!isNaN(maxDate) && parsedDate > maxDate) {
|
|
982
|
+
validation_status.status = true;
|
|
983
|
+
validation_status.validation.required = `Datetime must be before ${this.max}!`;
|
|
995
984
|
}
|
|
996
985
|
}
|
|
997
986
|
}
|
|
@@ -10403,7 +10392,7 @@ class CideEleDataGridComponent {
|
|
|
10403
10392
|
this.closeExportMenu();
|
|
10404
10393
|
}
|
|
10405
10394
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleDataGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10406
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleDataGridComponent, isStandalone: true, selector: "cide-ele-data-grid", inputs: { config: "config", templateRenderers: "templateRenderers", customFormatters: "customFormatters", actionHandlers: "actionHandlers", serverSidePagination: "serverSidePagination", totalServerItems: "totalServerItems", currentServerPage: "currentServerPage", currentServerPageSize: "currentServerPageSize", dragDropEnabled: "dragDropEnabled" }, outputs: { gridEvent: "gridEvent" }, usesOnChanges: true, ngImport: i0, template: " <!-- Data Grid Component -->\r\n <div class=\"data-grid-container tw-bg-white tw-shadow tw-rounded-lg tw-overflow-visible tw-flex tw-flex-col\" \r\n [ngClass]=\"[\r\n mergedConfig().tableClass || '',\r\n mergedConfig().fullHeight ? 'tw-h-full' : '',\r\n isDragDropEnabled() ? 'drag-drop-enabled' : '',\r\n isTreeEnabled() ? 'tree-enabled' : ''\r\n ]\">\r\n \r\n <!-- Header Section -->\r\n @if (mergedConfig().title || mergedConfig().subtitle) {\r\n <div class=\"tw-px-3 tw-py-2 tw-border-b tw-border-gray-200\">\r\n @if (mergedConfig().title) {\r\n <h3 class=\"tw-text-base tw-font-semibold tw-text-gray-900\">\r\n {{ mergedConfig().title }}\r\n </h3>\r\n }\r\n @if (mergedConfig().subtitle) {\r\n <p class=\"tw-text-sm tw-text-gray-600 tw-mt-0.5\">\r\n {{ mergedConfig().subtitle }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Search Section -->\r\n @if (searchConfig.enabled) {\r\n <div class=\"tw-px-3 tw-py-1.5 tw-border-b tw-border-gray-200\">\r\n <div class=\"tw-flex tw-items-center tw-justify-between\">\r\n <!-- Left Side: Search Input and Action Icons -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1.5\">\r\n <!-- Search Input - Apple Style -->\r\n <div class=\"tw-max-w-md data-grid-search-input\">\r\n <cide-ele-input [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\" id=\"search-input\" type=\"text\"\r\n [ngModel]=\"searchQuery()\"\r\n (ngModelChange)=\"updateSearchQuery($event)\"\r\n [placeholder]=\"searchConfig.placeholder\"\r\n [disabled]=\"loading() || isRefreshing()\"\r\n leadingIcon=\"search\"\r\n fill=\"outline\"\r\n size=\"sm\">\r\n </cide-ele-input>\r\n </div>\r\n \r\n <!-- Action Icons (Filter, Sort, Download) - Apple Style Compact -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1 data-grid-action-buttons\">\r\n <!-- Filter Button -->\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Filter\"\r\n (click)=\"onActionClick(null, { key: 'filter', label: 'Filter', icon: 'filter_list', variant: 'ghost', onClick: 'filter' })\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">filter_list</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Sort Button -->\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Sort\"\r\n (click)=\"onActionClick(null, { key: 'sort', label: 'Sort', icon: 'swap_vert', variant: 'ghost', onClick: 'sort' })\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">swap_vert</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Download/Export Button with Dropdown -->\r\n <div class=\"tw-relative\">\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Export\"\r\n (click)=\"toggleExportMenu($event)\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">file_download</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Export Dropdown Menu -->\r\n @if (showExportMenu()) {\r\n <div class=\"tw-absolute tw-right-0 tw-mt-1 tw-w-48 tw-bg-white tw-rounded-md tw-shadow-lg tw-border tw-border-gray-200 tw-z-50\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"tw-py-1\">\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100 tw-flex tw-items-center\"\r\n (click)=\"exportData('csv')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-2 tw-text-green-600\">description</cide-ele-icon>\r\n Export as CSV\r\n </button>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100 tw-flex tw-items-center\"\r\n (click)=\"exportData('excel')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-2 tw-text-blue-600\">file_present</cide-ele-icon>\r\n Export as Excel\r\n </button>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100 tw-flex tw-items-center\"\r\n (click)=\"exportData('pdf')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-2 tw-text-red-600\">picture_as_pdf</cide-ele-icon>\r\n Export as PDF\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- Right Side: Drag Order Actions -->\r\n @if (isDragDropEnabled() && (isDragging() || hasOrderChanged())) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <button cideEleButton \r\n variant=\"outline\" \r\n size=\"sm\" \r\n type=\"button\"\r\n (click)=\"onActionClick(null, { key: 'reset-order', label: 'Reset Order', icon: 'undo', variant: 'outline', onClick: 'resetOrder' })\"\r\n class=\"tw-text-blue-700 tw-border-blue-300 hover:tw-bg-blue-100\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">undo</cide-ele-icon>\r\n Reset Order\r\n </button>\r\n <button cideEleButton \r\n variant=\"primary\" \r\n size=\"sm\" \r\n type=\"button\"\r\n (click)=\"onActionClick(null, { key: 'save-order', label: 'Save Order', icon: 'save', variant: 'primary', onClick: 'saveOrder' })\"\r\n class=\"tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">save</cide-ele-icon>\r\n Save Order\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Table Section -->\r\n <div class=\"tw-overflow-x-auto tw-relative\"\r\n [ngClass]=\"{\r\n 'tw-flex-1 tw-min-h-0': mergedConfig().fullHeight,\r\n 'tw-overflow-y-auto': scrollConfig?.enabled,\r\n 'tw-max-h-full': scrollConfig?.enabled\r\n }\"\r\n [style.maxHeight]=\"scrollConfig?.enabled ? scrollConfig?.maxHeight : null\"\r\n [style.minHeight]=\"scrollConfig?.enabled ? scrollConfig?.minHeight : null\">\r\n <table class=\"tw-min-w-full tw-divide-y tw-divide-gray-200 tw-h-full tw-table-fixed\"\r\n [class.empty-table]=\"displayedData.length === 0\"\r\n [ngClass]=\"{\r\n 'tw-table-striped': mergedConfig().striped,\r\n 'tw-border': mergedConfig().bordered,\r\n 'tw-table-sm': mergedConfig().compact\r\n }\"\r\n style=\"table-layout: fixed;\">\r\n \r\n <!-- Table Header -->\r\n <thead class=\"tw-bg-gray-50\" \r\n [ngClass]=\"[\r\n mergedConfig().headerClass || '',\r\n scrollConfig?.enabled && scrollConfig?.stickyHeader ? 'tw-sticky tw-top-0 tw-z-10' : ''\r\n ]\">\r\n <tr>\r\n @for (column of visibleColumns(); track column.key) {\r\n <th\r\n class=\"tw-px-3 tw-py-2 tw-text-left tw-text-xs tw-font-medium tw-text-gray-500 tw-uppercase tw-tracking-wider tw-relative\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width),\r\n column.align === 'center' ? 'tw-text-center' : '',\r\n column.align === 'right' ? 'tw-text-right' : ''\r\n ]\"\r\n [title]=\"column.header\">\r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-gap-1\">\r\n <span class=\"tw-truncate tw-flex-1\">{{ column.header }}</span>\r\n \r\n <!-- Active Filter Indicator -->\r\n @if (isColumnFiltered(column.key)) {\r\n <div class=\"tw-inline-flex tw-items-center tw-px-1.5 tw-py-0.5 tw-rounded tw-bg-blue-100 tw-text-blue-700 tw-text-xs tw-font-medium tw-mr-1\">\r\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-mr-0.5\">filter_alt</cide-ele-icon>\r\n {{ getActiveFilterCount(column.key) }}\r\n </div>\r\n }\r\n \r\n <!-- Column Menu Trigger (Three Dots Icon) -->\r\n @if (mergedConfig().columnMenu?.enabled) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-600 hover:tw-text-gray-800 hover:tw-bg-gray-100 tw-transition-all column-menu-trigger\"\r\n [class.tw-text-blue-600]=\"isColumnMenuOpen(column.key) || isColumnFiltered(column.key)\"\r\n [class.tw-bg-blue-50]=\"isColumnFiltered(column.key)\"\r\n (click)=\"toggleColumnMenu(column.key, $event)\"\r\n title=\"Column options\">\r\n <cide-ele-icon class=\"tw-w-5 tw-h-4\">more_vert</cide-ele-icon>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Column Menu Dropdown -->\r\n @if (isColumnMenuOpen(column.key)) {\r\n <div class=\"column-menu-dropdown tw-absolute tw-z-50 tw-mt-2 tw-w-56 tw-rounded-lg tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5\">\r\n <div class=\"tw-py-1\">\r\n <!-- Sort Options -->\r\n @if (mergedConfig().columnMenu?.showSort && column.sortable !== false) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnSort(column, 'asc')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\r\n Ascending\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnSort(column, 'desc')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\r\n Descending\r\n </button>\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n }\r\n\r\n <!-- Filter Option -->\r\n @if (mergedConfig().columnMenu?.showFilter && column.filterable !== false) {\r\n <!-- Check if there's a custom filter renderer template -->\r\n @if (column.filterRenderer && templateRenderers[column.filterRenderer]) {\r\n <div class=\"tw-px-4 tw-py-2\">\r\n <ng-container [ngTemplateOutlet]=\"$any(templateRenderers[column.filterRenderer])\"\r\n [ngTemplateOutletContext]=\"{ $implicit: column, column: column, onFilter: onColumnFilter.bind(this) }\">\r\n </ng-container>\r\n </div>\r\n } @else {\r\n <!-- Excel-style Filter Button -->\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n [class.tw-bg-blue-50]=\"isFilterPanelOpen(column.key)\"\r\n (click)=\"toggleFilterPanel(column.key, $event)\">\r\n <div class=\"tw-flex tw-items-center\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">filter_list</cide-ele-icon>\r\n Filter\r\n </div>\r\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-gray-400\">\r\n {{ isFilterPanelOpen(column.key) ? 'expand_less' : 'expand_more' }}\r\n </cide-ele-icon>\r\n </button>\r\n \r\n <!-- Excel-style Filter Panel -->\r\n @if (isFilterPanelOpen(column.key)) {\r\n <div class=\"tw-px-2 tw-py-2 tw-bg-gray-50\" (click)=\"$event.stopPropagation()\">\r\n <!-- Search box -->\r\n <div class=\"tw-px-2 tw-mb-2\">\r\n <input\r\n type=\"text\"\r\n class=\"tw-w-full tw-px-2 tw-py-1 tw-text-xs tw-border tw-border-gray-300 tw-rounded focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500\"\r\n placeholder=\"Search...\"\r\n [(ngModel)]=\"filterSearchTerm\"\r\n (click)=\"$event.stopPropagation()\">\r\n </div>\r\n \r\n <!-- Select All / Deselect All -->\r\n <div class=\"tw-px-2 tw-mb-1 tw-flex tw-gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\r\n (click)=\"selectAllFilterValues(column); $event.stopPropagation()\">\r\n Select All\r\n </button>\r\n <span class=\"tw-text-xs tw-text-gray-400\">|</span>\r\n <button\r\n type=\"button\"\r\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\r\n (click)=\"deselectAllFilterValues(column); $event.stopPropagation()\">\r\n Clear\r\n </button>\r\n </div>\r\n \r\n <!-- Filter values list -->\r\n <div class=\"tw-max-h-48 tw-overflow-y-auto\">\r\n @for (item of getUniqueColumnValues(column); track item.value) {\r\n <label class=\"tw-flex tw-items-center tw-px-2 tw-py-1 tw-text-xs hover:tw-bg-white tw-cursor-pointer tw-rounded\">\r\n <input\r\n type=\"checkbox\"\r\n class=\"tw-mr-2 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\r\n [checked]=\"item.checked\"\r\n (change)=\"toggleFilterValue(column, item.value, $any($event.target).checked)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <span class=\"tw-flex-1 tw-truncate\">{{ item.label }}</span>\r\n <span class=\"tw-text-gray-400 tw-ml-1\">({{ item.count }})</span>\r\n </label>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n }\r\n\r\n <!-- Autosize Option -->\r\n @if (mergedConfig().columnMenu?.showAutosize) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAutosize(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">fit_screen</cide-ele-icon>\r\n Autosize\r\n </button>\r\n }\r\n\r\n <!-- Group By Column Option -->\r\n @if (mergedConfig().columnMenu?.showGroupBy && column.groupable !== false) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnGroupBy(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">group_work</cide-ele-icon>\r\n Group By Column\r\n </button>\r\n }\r\n\r\n <!-- Manage Columns Option -->\r\n @if (mergedConfig().columnMenu?.showManageColumns) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onManageColumns()\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">view_column</cide-ele-icon>\r\n Manage Columns\r\n </button>\r\n }\r\n\r\n <!-- Reset Columns Option -->\r\n @if (mergedConfig().columnMenu?.showResetColumns) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnReset()\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">refresh</cide-ele-icon>\r\n Reset Columns\r\n </button>\r\n }\r\n\r\n <!-- Hide Column Option -->\r\n @if (mergedConfig().columnMenu?.showHideColumn && column.hideable !== false) {\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnHide(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">visibility_off</cide-ele-icon>\r\n Hide Column\r\n </button>\r\n }\r\n\r\n <!-- Aggregation Select Option -->\r\n @if (mergedConfig().columnMenu?.showAggregation && column.aggregatable !== false) {\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n <div class=\"tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase\">Aggregation</div>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'sum')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">functions</cide-ele-icon>\r\n Sum\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'avg')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">analytics</cide-ele-icon>\r\n Average\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'count')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">tag</cide-ele-icon>\r\n Count\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'min')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\r\n Min\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'max')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\r\n Max\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <!-- Table Body -->\r\n <tbody class=\"tw-bg-white tw-divide-y tw-divide-gray-200\">\r\n @if (loading() || isRefreshing() || pageChangeLoading()) {\r\n <!-- Skeleton Loading Rows -->\r\n @for (skeletonItem of getSkeletonArray(); track $index) {\r\n <tr class=\"tw-animate-pulse tw-border-b tw-border-gray-200\">\r\n @for (column of columns; track column.key) {\r\n <td class=\"tw-px-3 tw-py-2 tw-whitespace-nowrap\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width)\r\n ]\">\r\n <div class=\"tw-h-2 tw-bg-gray-200 tw-rounded tw-w-3/4\"></div>\r\n </td>\r\n }\r\n </tr>\r\n }\r\n } @else {\r\n @for (item of displayedData; track trackByFn($index, item)) {\r\n <tr class=\"tw-group hover:tw-bg-gray-50 tw-border-b-2 tw-border-gray-200\"\r\n [ngClass]=\"[\r\n mergedConfig().rowClass || '',\r\n isRefreshing() ? 'tw-opacity-60 tw-pointer-events-none' : '',\r\n isDragDropEnabled() ? 'tw-cursor-move tw-border-2 tw-border-transparent' : '',\r\n !isDragDropEnabled() ? 'tw-transition-colors tw-duration-150' : '',\r\n isTreeEnabled() ? getTreeLevelClass(item) : ''\r\n ]\"\r\n [style.border-color]=\"isDragOverRow === $index ? '#3b82f6' : 'transparent'\"\r\n [style.background-color]=\"isDragOverRow === $index ? '#eff6ff' : ''\"\r\n (click)=\"onRowClick(item)\"\r\n (keydown.enter)=\"onRowClick(item)\"\r\n (keydown.space)=\"onRowClick(item)\"\r\n [class.tw-cursor-pointer]=\"mergedConfig().onRowClick && !isDragDropEnabled()\"\r\n [tabindex]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 0 : -1\"\r\n [attr.role]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'button' : null\"\r\n [attr.aria-label]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'Select row' : null\"\r\n [draggable]=\"isDragDropEnabled()\"\r\n (dragstart)=\"onDragStart($event, item, $index)\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event, item, $index)\"\r\n (dragend)=\"onDragEnd($event)\">\r\n \r\n @for (column of columns; track column.key) {\r\n <td class=\"tw-pr-3 tw-py-1 tw-relative\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width),\r\n mergedConfig().cellClass || '',\r\n column.align === 'center' ? 'tw-text-center' : '',\r\n column.align === 'right' ? 'tw-text-right' : '',\r\n column.truncate !== false ? 'tw-whitespace-nowrap' : 'tw-whitespace-normal'\r\n ]\"\r\n [style.paddingLeft]=\"isTreeEnabled() && $index === 0 ? getTreeIndentStyle(item) : '12px'\"\r\n [style.maxWidth]=\"getColumnMaxWidthClass(column.width) === 'tw-max-w-xs' ? '200px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-sm' ? '300px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-md' ? '400px' : 'none'\"\r\n [style.minWidth]=\"isTreeEnabled() && $index === 0 ? '150px' : '100px'\">\r\n <!-- Tree Expand/Collapse Button (only for first column when tree is enabled) -->\r\n @if (isTreeEnabled() && $index === 0) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <!-- Tree Indentation -->\r\n <div class=\"tw-flex tw-items-center\">\r\n @if (hasChildren(item)) {\r\n <button \r\n variant=\"outline\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, { key: 'toggle-expand', label: 'Toggle', icon: '', variant: 'ghost', onClick: 'toggle-expand' }); $event.stopPropagation()\"\r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-text-gray-500 hover:tw-text-gray-700 tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [title]=\"isItemExpanded(item) ? 'Collapse' : 'Expand'\">\r\n <cide-ele-icon \r\n class=\"tw-w-3 tw-h-3\"\r\n [class.tw-transition-transform]=\"!isDragDropEnabled()\"\r\n [class.tw-rotate-90]=\"isItemExpanded(item)\"\r\n size=\"xs\">\r\n chevron_right\r\n </cide-ele-icon>\r\n </button>\r\n } @else {\r\n <div class=\"tw-w-8 tw-h-5 tw-flex tw-items-center tw-justify-center\">\r\n <!-- <div class=\"tw-w-1 tw-h-1 tw-bg-gray-300 tw-rounded-full\"></div> -->\r\n </div>\r\n }\r\n </div>\r\n \r\n <!-- Cell Content -->\r\n <div class=\"tw-flex-1 tw-w-full\">\r\n @if (column.type === 'text') {\r\n <p class=\"tw-text-sm tw-text-gray-900\"\r\n [class.tw-truncate]=\"column.truncate\"\r\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </p>\r\n } @else if (column.type === 'number') {\r\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'date') {\r\n <span class=\"tw-text-sm tw-text-gray-600\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'boolean') {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\r\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\r\n </span>\r\n } @else if (column.type === 'status' && column.statusConfig) {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\r\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\r\n </span>\r\n } @else if (column.type === 'actions' && column.actions) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n @for (action of column.actions; track action.key) {\r\n <button\r\n cideEleButton\r\n [variant]=\"action.variant || 'ghost'\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\r\n [title]=\"action.tooltip || action.label\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [ngClass]=\"{\r\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\r\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\r\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\r\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\r\n }\">\r\n @if (action.icon) {\r\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\r\n </svg>\r\n }\r\n {{ action.label }}\r\n </button>\r\n }\r\n </div>\r\n } @else if (column.type === 'custom') {\r\n <!-- Template Renderer -->\r\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\r\n <ng-container \r\n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\r\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\r\n </ng-container>\r\n }\r\n <!-- Default rendering -->\r\n @else {\r\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <!-- Regular cell content (non-tree or non-first column) -->\r\n @if (column.type === 'text') {\r\n <p class=\"tw-text-sm tw-text-gray-900\"\r\n [class.tw-truncate]=\"column.truncate\"\r\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </p>\r\n } @else if (column.type === 'number') {\r\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'date') {\r\n <span class=\"tw-text-sm tw-text-gray-600\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'boolean') {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\r\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\r\n </span>\r\n } @else if (column.type === 'status' && column.statusConfig) {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\r\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\r\n </span>\r\n } @else if (column.type === 'actions' && column.actions) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n @for (action of column.actions; track action.key) {\r\n <button\r\n cideEleButton\r\n [variant]=\"action.variant || 'ghost'\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\r\n [title]=\"action.tooltip || action.label\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [ngClass]=\"{\r\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\r\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\r\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\r\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\r\n }\">\r\n @if (action.icon) {\r\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\r\n </svg>\r\n }\r\n {{ action.label }}\r\n </button>\r\n }\r\n </div>\r\n } @else if (column.type === 'custom') {\r\n <!-- Template Renderer -->\r\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\r\n <ng-container \r\n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\r\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\r\n </ng-container>\r\n }\r\n <!-- Default rendering -->\r\n @else {\r\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n \r\n <!-- Empty State -->\r\n @if (displayedData.length === 0) {\r\n <tr class=\"tw-h-full\">\r\n <td [attr.colspan]=\"columns.length\" class=\"tw-px-6 tw-py-22 tw-text-center tw-h-full tw-align-middle\">\r\n <div class=\"tw-text-gray-500 tw-flex tw-flex-col tw-items-center tw-justify-center tw-min-h-[300px]\">\r\n <svg class=\"tw-mx-auto tw-h-12 tw-w-12 tw-text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path>\r\n </svg>\r\n <h3 class=\"tw-mt-2 tw-text-sm tw-font-medium tw-text-gray-900\">No data found</h3>\r\n <p class=\"tw-mt-1 tw-text-sm tw-text-gray-500\">\r\n @if (searchQuery()) {\r\n No results match your search criteria.\r\n } @else {\r\n There are no items to display.\r\n }\r\n </p>\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <!-- Pagination Section -->\r\n @if (paginationConfig.enabled && totalItems() > 0) {\r\n <div class=\"tw-px-3 tw-py-0 tw-border-t tw-border-gray-200 tw-bg-white tw-relative tw-z-20\"\r\n [class.tw-opacity-60]=\"isRefreshing()\">\r\n \r\n <!-- Results Info and Page Size -->\r\n <div class=\"tw-flex tw-flex-col sm:tw-flex-row tw-justify-between tw-items-start sm:tw-items-center tw-space-y-1 sm:tw-space-y-0\">\r\n \r\n <!-- Results Info -->\r\n @if (paginationConfig.showPageInfo) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-3\">\r\n <p class=\"tw-text-sm tw-text-gray-700\">\r\n Showing {{ getItemRangeText() }} results\r\n </p>\r\n \r\n <!-- Page Size Selector -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-500\">view_list</cide-ele-icon>\r\n <span class=\"tw-text-sm tw-text-gray-700\">Per page:</span>\r\n <div class=\"tw-w-16 tw-relative\">\r\n <cide-ele-select\r\n [labelHide]=\"true\"\r\n [ngModel]=\"pageSize()\"\r\n (ngModelChange)=\"updatePageSize($event)\"\r\n [options]=\"getPageSizeOptions()\"\r\n [disabled]=\"isRefreshing()\"\r\n fill=\"outline\"\r\n size=\"xs\"\r\n class=\"tw-z-30\">\r\n </cide-ele-select>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Pagination Controls -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n \r\n <!-- Previous/Next and Page Numbers -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\r\n \r\n <!-- First Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(1)\"\r\n [disabled]=\"currentPage() === 1 || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"First page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">first_page</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Previous Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"previousPage()\"\r\n [disabled]=\"!hasPreviousPage() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Previous page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_left</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Page Numbers -->\r\n @for (page of getEnhancedPageNumbers(); track page) {\r\n @if (page === '...') {\r\n <span class=\"tw-px-2 tw-py-2 tw-text-sm tw-text-gray-500\">...</span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(page)\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n [ngClass]=\"{\r\n 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700': currentPage() === page,\r\n 'tw-bg-white tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-50': currentPage() !== page\r\n }\"\r\n [title]=\"'Page ' + page\">\r\n {{ page }}\r\n </button>\r\n }\r\n }\r\n \r\n <!-- Next Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"nextPage()\"\r\n [disabled]=\"!hasNextPage() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Next page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_right</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Last Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(totalPages())\"\r\n [disabled]=\"currentPage() === totalPages() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Last page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">last_page</cide-ele-icon>\r\n </button>\r\n \r\n </div>\r\n\r\n <!-- Quick Jump and Refresh -->\r\n @if (paginationConfig.showQuickJump || paginationConfig.showRefresh) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-1 tw-border-l tw-border-gray-200 tw-pl-2\">\r\n \r\n <!-- Quick Jump -->\r\n @if (paginationConfig.showQuickJump) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\r\n <span class=\"tw-text-sm tw-text-gray-700\">Go to:</span>\r\n <div class=\"tw-w-16\">\r\n <cide-ele-input id=\"jump-to-page-input\" type=\"number\" [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\"\r\n [(ngModel)]=\"jumpToPage\" [min]=\"1\" [max]=\"totalPages()\"\r\n [disabled]=\"isRefreshing()\"\r\n size=\"xs\"\r\n (keydown.enter)=\"onJumpToPage()\">\r\n </cide-ele-input>\r\n </div>\r\n <button\r\n type=\"button\"\r\n (click)=\"onJumpToPage()\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-bg-gray-100 tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-200 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Go to page\">\r\n Go\r\n </button>\r\n </div>\r\n }\r\n \r\n <!-- Refresh Button -->\r\n @if (paginationConfig.showRefresh) {\r\n <button\r\n type=\"button\"\r\n (click)=\"onRefresh()\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Refresh\">\r\n @if (isRefreshing()) {\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4 tw-animate-spin\">refresh</cide-ele-icon>\r\n } @else {\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">refresh</cide-ele-icon>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n \r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [".data-grid-container{width:100%;display:flex;flex-direction:column;min-height:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:#1f2937;background-color:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.data-grid-container.tw-h-full{height:100%;min-height:100%}.data-grid-container .tw-overflow-x-auto{scrollbar-width:thin;scrollbar-color:#d1d5db #f9fafb}.data-grid-container .tw-overflow-x-auto.tw-flex-1{flex:1;min-height:0}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar{height:6px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-track{background:#f9fafb}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:3px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb:hover{background:#9ca3af}.data-grid-container table{min-height:300px;border-collapse:separate;border-spacing:0;width:100%;background-color:#fff}.data-grid-container thead{background:linear-gradient(180deg,#fafafa,#f7f7f7);border-bottom:1px solid rgba(0,0,0,.06);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.data-grid-container thead th{background:transparent;color:#6b7280;font-weight:500;font-size:12px;text-transform:none;letter-spacing:-.01em;padding:4px 10px;border-bottom:1px solid rgba(0,0,0,.06);text-align:left;white-space:nowrap;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container thead th:first-child{padding-left:12px}.data-grid-container thead th:last-child{padding-right:12px}.data-grid-container thead th:hover{background-color:#00000005}.data-grid-container thead th .column-menu-trigger{opacity:1;transition:all .2s cubic-bezier(.4,0,.2,1);margin-left:4px;cursor:pointer;padding:2px;border-radius:4px}.data-grid-container thead th .column-menu-trigger:hover{background-color:#0000000f}.data-grid-container tbody{background-color:#fff}.data-grid-container tbody td{padding:6px 10px;border-bottom:1px solid rgba(0,0,0,.03);color:#1f2937;font-size:13px;vertical-align:middle;line-height:1.5;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody td:first-child{padding-left:12px}.data-grid-container tbody td:last-child{padding-right:12px}.data-grid-container tbody tr{background-color:#fff;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody tr:hover{background-color:#00000005;transform:scale(1.001)}.data-grid-container tbody tr:hover td{border-bottom-color:#0000000a}.data-grid-container tbody tr:active{background-color:#00000008;transform:scale(.999)}.data-grid-container.tw-h-full table,.data-grid-container.tw-h-full tbody{height:100%}.data-grid-container.tw-table-striped tbody tr:nth-child(2n){background-color:#00000004}.data-grid-container.tw-table-striped tbody tr:nth-child(2n):hover{background-color:#00000008;transform:scale(1.001)}.data-grid-container.tw-table-sm thead th{padding:8px 10px;font-size:11px}.data-grid-container.tw-table-sm thead th:first-child{padding-left:16px}.data-grid-container.tw-table-sm thead th:last-child{padding-right:16px}.data-grid-container.tw-table-sm tbody td{padding:8px 10px;font-size:12px;line-height:1.4}.data-grid-container.tw-table-sm tbody td:first-child{padding-left:16px}.data-grid-container.tw-table-sm tbody td:last-child{padding-right:16px}.data-grid-container tbody tr.tw-cursor-pointer:hover{background-color:#e5f3ff}.data-grid-container.loading-overlay{position:relative}.data-grid-container.loading-overlay:after{content:\"\";position:absolute;inset:0;background:#ffffffd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;animation:fadeIn .2s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.data-grid-container .tw-animate-pulse div{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.data-grid-container .data-grid-action-buttons button{position:relative;border:1px solid rgba(0,0,0,.08);background:transparent;outline:none;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:6px;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button cide-ele-icon{color:#6b7280;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled){background-color:#0000000a;border-color:#0000001f}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled) cide-ele-icon{color:#374151;transform:scale(1.05)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled){background-color:#00000014;border-color:#00000026;transform:scale(.95)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled) cide-ele-icon{transform:scale(.98)}.data-grid-container .data-grid-action-buttons button:focus-visible{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f61f}.data-grid-container .data-grid-action-buttons button:disabled{cursor:not-allowed;opacity:.3;background:transparent!important;border-color:#0000000d}.data-grid-container .action-buttons{display:flex;gap:.25rem}.data-grid-container .action-buttons button{transition:all .15s ease-in-out}.data-grid-container .action-buttons button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.data-grid-container .action-buttons button:disabled{cursor:not-allowed;opacity:.5}.data-grid-container .pagination-controls{padding:12px 20px;border-top:1px solid rgba(0,0,0,.06);background:linear-gradient(180deg,#fafafa,#f7f7f7)}.data-grid-container .pagination-controls button{transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:6px}.data-grid-container .pagination-controls button:hover:not(:disabled){background-color:#0000000a;transform:scale(1.02)}.data-grid-container .pagination-controls button:active:not(:disabled){transform:scale(.98);background-color:#00000014}.data-grid-container .pagination-controls button:disabled{cursor:not-allowed;opacity:.3}.data-grid-container .pagination-controls button.active{background-color:#3b82f61a;color:#3b82f6;font-weight:500;box-shadow:0 0 0 1px #3b82f633}.data-grid-container .pagination-controls input[type=number]{transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(0,0,0,.06);border-radius:6px}.data-grid-container .pagination-controls input[type=number]:hover{border-color:#0000001a}.data-grid-container .pagination-controls input[type=number]:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .status-badge{font-weight:500;letter-spacing:-.01em;padding:4px 12px;border-radius:12px;font-size:12px;display:inline-flex;align-items:center;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .status-badge.active{background-color:#34d39926;color:#059669;border:1px solid rgba(52,211,153,.3)}.data-grid-container .status-badge.active:hover{background-color:#34d39933}.data-grid-container .status-badge.inactive{background-color:#f8717126;color:#dc2626;border:1px solid rgba(248,113,113,.3)}.data-grid-container .status-badge.inactive:hover{background-color:#f8717133}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper{transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:hover:not(:has(input:disabled)){background-color:#00000005}.data-grid-container .data-grid-search-input ::ng-deep input{font-size:13px;font-weight:400;color:#1f2937;transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep input::placeholder{color:#9ca3af;font-weight:400}.data-grid-container .data-grid-search-input ::ng-deep input:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep input:disabled{background-color:#00000005;color:#9ca3af;cursor:not-allowed}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-leading-icon cide-ele-icon{color:#9ca3af;transition:color .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep input:focus~.cide-input-leading-icon cide-ele-icon,.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:has(input:focus) .cide-input-leading-icon cide-ele-icon{color:#6b7280}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]{border:1px solid #e5e7eb;background-color:#fafafa;border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:hover:not(:has(input:disabled)){border-color:#d1d5db;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:has(input:focus){border-color:#3b82f64d;background-color:#fff}.data-grid-container .search-input{position:relative}.data-grid-container .search-input input{transition:all .15s ease-in-out}.data-grid-container .search-input input:focus{box-shadow:0 0 0 3px #3b82f61a}.data-grid-container .search-input .search-icon{pointer-events:none}.data-grid-container .column-menu-dropdown{animation:dropdownFadeIn .15s cubic-bezier(.4,0,.2,1);box-shadow:0 10px 25px #0000001a,0 4px 10px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(0,0,0,.05)}.data-grid-container .column-menu-dropdown button{font-size:13px;font-weight:400;letter-spacing:-.01em;text-align:left;transition:all .15s cubic-bezier(.4,0,.2,1)}.data-grid-container .column-menu-dropdown button:hover{background-color:#3b82f60d;color:#1f2937}.data-grid-container .column-menu-dropdown button:hover cide-ele-icon{color:#3b82f6}.data-grid-container .column-menu-dropdown button:active{background-color:#3b82f61a;transform:scale(.98)}.data-grid-container .column-menu-dropdown .tw-uppercase{letter-spacing:.05em}@keyframes dropdownFadeIn{0%{opacity:0;transform:translateY(-8px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.data-grid-container .empty-state{padding:4rem 2rem;text-align:center}.data-grid-container .empty-state svg{margin:0 auto 1.5rem;opacity:.3;transition:all .3s cubic-bezier(.4,0,.2,1)}.data-grid-container .empty-state svg:hover{opacity:.5;transform:scale(1.05)}.data-grid-container .empty-state h3{margin-bottom:.75rem;font-weight:600;color:#374151;font-size:16px;letter-spacing:-.01em}.data-grid-container .empty-state p{color:#6b7280;font-size:14px;line-height:1.5}.data-grid-container.tw-h-full tbody tr:only-child td{height:100%;vertical-align:middle}.data-grid-container.tw-h-full table.empty-table{height:100%}.data-grid-container.tw-h-full tbody tr:only-child{height:100%}@media (max-width: 640px){.data-grid-container .tw-px-6{padding-left:1rem;padding-right:1rem}.data-grid-container .pagination-controls{flex-direction:column;gap:1rem}.data-grid-container .pagination-controls .flex{justify-content:center}.data-grid-container .pagination-info{display:none}.data-grid-container .search-actions{flex-direction:column;gap:1rem}}@media (prefers-color-scheme: dark){.data-grid-container.dark-mode{background-color:#1f2937;color:#f9fafb}.data-grid-container.dark-mode table{background-color:#1f2937}.data-grid-container.dark-mode thead{background-color:#374151}.data-grid-container.dark-mode tbody tr{border-color:#374151}.data-grid-container.dark-mode tbody tr:hover{background-color:#374151}.data-grid-container.dark-mode .tw-text-gray-900{color:#f9fafb!important}.data-grid-container.dark-mode .tw-text-gray-600{color:#d1d5db!important}.data-grid-container.dark-mode .tw-text-gray-500{color:#9ca3af!important}}.data-grid-container.drag-drop-enabled tbody tr{transition:all .2s ease}.data-grid-container.drag-drop-enabled tbody tr:hover{background-color:#f3f4f6}.data-grid-container.drag-drop-enabled tbody tr.tw-opacity-50{background-color:#dbeafecc;border:2px dashed #3b82f6;border-radius:4px}.data-grid-container.drag-drop-enabled tbody tr.tw-bg-blue-50{background-color:#eff6ffe6}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]{cursor:move;position:relative}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:hover{background-color:#f8fafc;box-shadow:0 1px 3px #0000001a}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:active{cursor:grabbing}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]{position:relative}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]:before{content:\"\";position:absolute;top:-2px;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);z-index:10}.data-grid-container.tree-enabled tbody tr td:first-child{position:relative}.data-grid-container.tree-enabled tbody tr td:first-child button{transition:all .15s ease}.data-grid-container.tree-enabled tbody tr td:first-child button:hover{background-color:#f3f4f6;border-radius:2px}.data-grid-container.tree-enabled tbody tr td:first-child button svg{transition:transform .2s ease}.data-grid-container.tree-enabled tbody tr[style*=padding-left]{border-left:2px solid transparent}.data-grid-container.tree-enabled tbody tr[style*=padding-left]:hover{border-left-color:#e5e7eb}.tree-level-0{border-bottom:2px solid #e5e7eb!important;background-color:#fff}.tree-level-0:hover{background-color:#f5f5f5!important}.tree-level-0 td:first-child{font-weight:600}.tree-level-1{border-bottom:1px solid #d1d5db!important;background-color:#fafafa}.tree-level-1:hover{background-color:#f1f5f9!important}.tree-level-1 td:first-child{font-weight:500}.tree-level-2{border-bottom:1px solid #d1d5db!important;background-color:#f9fafb}.tree-level-2:hover{background-color:#f3f4f6!important}.tree-level-2 td:first-child{font-weight:400}.tree-level-deep{border-bottom:1px solid #e5e7eb!important;background-color:#fefefe}.tree-level-deep:hover{background-color:#f9fafb!important}.tree-level-deep td:first-child{font-weight:300}table td{box-sizing:border-box;word-wrap:break-word;overflow-wrap:break-word}table td.tw-overflow-hidden{overflow:hidden;text-overflow:ellipsis}table td:first-child{min-width:150px}table td:first-child>div{display:flex;align-items:center;min-width:0}table td:first-child>div .tw-flex{min-width:0}table td .tw-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "size"], outputs: ["ngModelChange"] }, { kind: "component", type: CideSelectComponent, selector: "cide-ele-select", inputs: ["label", "labelHide", "placeholder", "helperText", "errorText", "required", "disabled", "id", "ngModel", "size", "fill", "labelPlacement", "labelDir", "leadingIcon", "trailingIcon", "clearInput", "options", "multiple", "searchable", "showSearchInput", "loading", "valueKey", "labelKey"], outputs: ["ngModelChange", "change", "searchChange"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton]", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }] });
|
|
10395
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.7", type: CideEleDataGridComponent, isStandalone: true, selector: "cide-ele-data-grid", inputs: { config: "config", templateRenderers: "templateRenderers", customFormatters: "customFormatters", actionHandlers: "actionHandlers", serverSidePagination: "serverSidePagination", totalServerItems: "totalServerItems", currentServerPage: "currentServerPage", currentServerPageSize: "currentServerPageSize", dragDropEnabled: "dragDropEnabled" }, outputs: { gridEvent: "gridEvent" }, usesOnChanges: true, ngImport: i0, template: " <!-- Data Grid Component -->\r\n <div class=\"data-grid-container tw-bg-white tw-shadow tw-rounded-lg tw-overflow-visible tw-flex tw-flex-col\" \r\n [ngClass]=\"[\r\n mergedConfig().tableClass || '',\r\n mergedConfig().fullHeight ? 'tw-h-full' : '',\r\n isDragDropEnabled() ? 'drag-drop-enabled' : '',\r\n isTreeEnabled() ? 'tree-enabled' : ''\r\n ]\">\r\n \r\n <!-- Header Section -->\r\n @if (mergedConfig().title || mergedConfig().subtitle) {\r\n <div class=\"tw-px-3 tw-py-2 tw-border-b tw-border-gray-200\">\r\n @if (mergedConfig().title) {\r\n <h3 class=\"tw-text-base tw-font-semibold tw-text-gray-900\">\r\n {{ mergedConfig().title }}\r\n </h3>\r\n }\r\n @if (mergedConfig().subtitle) {\r\n <p class=\"tw-text-sm tw-text-gray-600 tw-mt-0.5\">\r\n {{ mergedConfig().subtitle }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Search Section -->\r\n @if (searchConfig.enabled) {\r\n <div class=\"tw-px-3 tw-py-1.5 tw-border-b tw-border-gray-200\">\r\n <div class=\"tw-flex tw-items-center tw-justify-between\">\r\n <!-- Left Side: Search Input and Action Icons -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1.5\">\r\n <!-- Search Input - Apple Style -->\r\n <div class=\"tw-max-w-md data-grid-search-input\">\r\n <cide-ele-input [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\" id=\"search-input\" type=\"text\"\r\n [ngModel]=\"searchQuery()\"\r\n (ngModelChange)=\"updateSearchQuery($event)\"\r\n [placeholder]=\"searchConfig.placeholder\"\r\n [disabled]=\"loading() || isRefreshing()\"\r\n leadingIcon=\"search\"\r\n fill=\"outline\"\r\n size=\"sm\">\r\n </cide-ele-input>\r\n </div>\r\n \r\n <!-- Action Icons (Filter, Sort, Download) - Apple Style Compact -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1 data-grid-action-buttons\">\r\n <!-- Filter Button -->\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Filter\"\r\n (click)=\"onActionClick(null, { key: 'filter', label: 'Filter', icon: 'filter_list', variant: 'ghost', onClick: 'filter' })\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">filter_list</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Download/Export Button with Dropdown -->\r\n <div class=\"tw-relative\">\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Export\"\r\n (click)=\"toggleExportMenu($event)\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">file_download</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Export Dropdown Menu - Improved Design -->\r\n @if (showExportMenu()) {\r\n <div class=\"tw-absolute tw-right-0 tw-mt-2 tw-w-56 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"tw-py-1.5\">\r\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200\">\r\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Export Options</span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-green-50 hover:tw-text-green-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\r\n (click)=\"exportData('csv')\">\r\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-green-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-green-600\">description</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-flex-1\">\r\n <div class=\"tw-font-medium\">CSV</div>\r\n <div class=\"tw-text-xs tw-text-gray-500\">Comma separated values</div>\r\n </div>\r\n </button>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-blue-50 hover:tw-text-blue-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\r\n (click)=\"exportData('excel')\">\r\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">table_chart</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-flex-1\">\r\n <div class=\"tw-font-medium\">Excel</div>\r\n <div class=\"tw-text-xs tw-text-gray-500\">Microsoft Excel format</div>\r\n </div>\r\n </button>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-red-50 hover:tw-text-red-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\r\n (click)=\"exportData('pdf')\">\r\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-red-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-red-600\">picture_as_pdf</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-flex-1\">\r\n <div class=\"tw-font-medium\">PDF</div>\r\n <div class=\"tw-text-xs tw-text-gray-500\">Portable document format</div>\r\n </div>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- Right Side: Drag Order Actions -->\r\n @if (isDragDropEnabled() && (isDragging() || hasOrderChanged())) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <button cideEleButton \r\n variant=\"outline\" \r\n size=\"sm\" \r\n type=\"button\"\r\n (click)=\"onActionClick(null, { key: 'reset-order', label: 'Reset Order', icon: 'undo', variant: 'outline', onClick: 'resetOrder' })\"\r\n class=\"tw-text-blue-700 tw-border-blue-300 hover:tw-bg-blue-100\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">undo</cide-ele-icon>\r\n Reset Order\r\n </button>\r\n <button cideEleButton \r\n variant=\"primary\" \r\n size=\"sm\" \r\n type=\"button\"\r\n (click)=\"onActionClick(null, { key: 'save-order', label: 'Save Order', icon: 'save', variant: 'primary', onClick: 'saveOrder' })\"\r\n class=\"tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">save</cide-ele-icon>\r\n Save Order\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Table Section -->\r\n <div class=\"tw-overflow-x-auto tw-relative\"\r\n [ngClass]=\"{\r\n 'tw-flex-1 tw-min-h-0': mergedConfig().fullHeight,\r\n 'tw-overflow-y-auto': scrollConfig?.enabled,\r\n 'tw-max-h-full': scrollConfig?.enabled\r\n }\"\r\n [style.maxHeight]=\"scrollConfig?.enabled ? scrollConfig?.maxHeight : null\"\r\n [style.minHeight]=\"scrollConfig?.enabled ? scrollConfig?.minHeight : null\">\r\n <table class=\"tw-min-w-full tw-divide-y tw-divide-gray-200 tw-h-full tw-table-fixed\"\r\n [class.empty-table]=\"displayedData.length === 0\"\r\n [ngClass]=\"{\r\n 'tw-table-striped': mergedConfig().striped,\r\n 'tw-border': mergedConfig().bordered,\r\n 'tw-table-sm': mergedConfig().compact\r\n }\"\r\n style=\"table-layout: fixed;\">\r\n \r\n <!-- Table Header -->\r\n <thead class=\"tw-bg-gray-50\" \r\n [ngClass]=\"[\r\n mergedConfig().headerClass || '',\r\n scrollConfig?.enabled && scrollConfig?.stickyHeader ? 'tw-sticky tw-top-0 tw-z-10' : ''\r\n ]\">\r\n <tr>\r\n @for (column of visibleColumns(); track column.key) {\r\n <th\r\n class=\"tw-px-3 tw-py-2 tw-text-left tw-text-xs tw-font-medium tw-text-gray-500 tw-uppercase tw-tracking-wider tw-relative\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width),\r\n column.align === 'center' ? 'tw-text-center' : '',\r\n column.align === 'right' ? 'tw-text-right' : ''\r\n ]\"\r\n [title]=\"column.header\">\r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-gap-1\">\r\n <span class=\"tw-truncate tw-flex-1\">{{ column.header }}</span>\r\n \r\n <!-- Active Filter Indicator -->\r\n @if (isColumnFiltered(column.key)) {\r\n <div class=\"tw-inline-flex tw-items-center tw-px-1.5 tw-py-0.5 tw-rounded tw-bg-blue-100 tw-text-blue-700 tw-text-xs tw-font-medium tw-mr-1\">\r\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-mr-0.5\">filter_alt</cide-ele-icon>\r\n {{ getActiveFilterCount(column.key) }}\r\n </div>\r\n }\r\n \r\n <!-- Column Menu Trigger (Three Dots Icon) -->\r\n @if (mergedConfig().columnMenu?.enabled) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-600 hover:tw-text-gray-800 hover:tw-bg-gray-100 tw-transition-all column-menu-trigger\"\r\n [class.tw-text-blue-600]=\"isColumnMenuOpen(column.key) || isColumnFiltered(column.key)\"\r\n [class.tw-bg-blue-50]=\"isColumnFiltered(column.key)\"\r\n (click)=\"toggleColumnMenu(column.key, $event)\"\r\n title=\"Column options\">\r\n <cide-ele-icon class=\"tw-w-5 tw-h-4\">more_vert</cide-ele-icon>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Column Menu Dropdown -->\r\n @if (isColumnMenuOpen(column.key)) {\r\n <div class=\"column-menu-dropdown tw-absolute tw-z-50 tw-mt-2 tw-w-56 tw-rounded-lg tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5\">\r\n <div class=\"tw-py-1\">\r\n <!-- Sort Options -->\r\n @if (mergedConfig().columnMenu?.showSort && column.sortable !== false) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnSort(column, 'asc')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\r\n Ascending\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnSort(column, 'desc')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\r\n Descending\r\n </button>\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n }\r\n\r\n <!-- Filter Option -->\r\n @if (mergedConfig().columnMenu?.showFilter && column.filterable !== false) {\r\n <!-- Check if there's a custom filter renderer template -->\r\n @if (column.filterRenderer && templateRenderers[column.filterRenderer]) {\r\n <div class=\"tw-px-4 tw-py-2\">\r\n <ng-container [ngTemplateOutlet]=\"$any(templateRenderers[column.filterRenderer])\"\r\n [ngTemplateOutletContext]=\"{ $implicit: column, column: column, onFilter: onColumnFilter.bind(this) }\">\r\n </ng-container>\r\n </div>\r\n } @else {\r\n <!-- Excel-style Filter Button -->\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n [class.tw-bg-blue-50]=\"isFilterPanelOpen(column.key)\"\r\n (click)=\"toggleFilterPanel(column.key, $event)\">\r\n <div class=\"tw-flex tw-items-center\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">filter_list</cide-ele-icon>\r\n Filter\r\n </div>\r\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-gray-400\">\r\n {{ isFilterPanelOpen(column.key) ? 'expand_less' : 'expand_more' }}\r\n </cide-ele-icon>\r\n </button>\r\n \r\n <!-- Excel-style Filter Panel -->\r\n @if (isFilterPanelOpen(column.key)) {\r\n <div class=\"tw-px-2 tw-py-2 tw-bg-gray-50\" (click)=\"$event.stopPropagation()\">\r\n <!-- Search box -->\r\n <div class=\"tw-px-2 tw-mb-2\">\r\n <input\r\n type=\"text\"\r\n class=\"tw-w-full tw-px-2 tw-py-1 tw-text-xs tw-border tw-border-gray-300 tw-rounded focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500\"\r\n placeholder=\"Search...\"\r\n [(ngModel)]=\"filterSearchTerm\"\r\n (click)=\"$event.stopPropagation()\">\r\n </div>\r\n \r\n <!-- Select All / Deselect All -->\r\n <div class=\"tw-px-2 tw-mb-1 tw-flex tw-gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\r\n (click)=\"selectAllFilterValues(column); $event.stopPropagation()\">\r\n Select All\r\n </button>\r\n <span class=\"tw-text-xs tw-text-gray-400\">|</span>\r\n <button\r\n type=\"button\"\r\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\r\n (click)=\"deselectAllFilterValues(column); $event.stopPropagation()\">\r\n Clear\r\n </button>\r\n </div>\r\n \r\n <!-- Filter values list -->\r\n <div class=\"tw-max-h-48 tw-overflow-y-auto\">\r\n @for (item of getUniqueColumnValues(column); track item.value) {\r\n <label class=\"tw-flex tw-items-center tw-px-2 tw-py-1 tw-text-xs hover:tw-bg-white tw-cursor-pointer tw-rounded\">\r\n <input\r\n type=\"checkbox\"\r\n class=\"tw-mr-2 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\r\n [checked]=\"item.checked\"\r\n (change)=\"toggleFilterValue(column, item.value, $any($event.target).checked)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <span class=\"tw-flex-1 tw-truncate\">{{ item.label }}</span>\r\n <span class=\"tw-text-gray-400 tw-ml-1\">({{ item.count }})</span>\r\n </label>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n }\r\n\r\n <!-- Autosize Option -->\r\n @if (mergedConfig().columnMenu?.showAutosize) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAutosize(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">fit_screen</cide-ele-icon>\r\n Autosize\r\n </button>\r\n }\r\n\r\n <!-- Group By Column Option -->\r\n @if (mergedConfig().columnMenu?.showGroupBy && column.groupable !== false) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnGroupBy(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">group_work</cide-ele-icon>\r\n Group By Column\r\n </button>\r\n }\r\n\r\n <!-- Manage Columns Option -->\r\n @if (mergedConfig().columnMenu?.showManageColumns) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onManageColumns()\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">view_column</cide-ele-icon>\r\n Manage Columns\r\n </button>\r\n }\r\n\r\n <!-- Reset Columns Option -->\r\n @if (mergedConfig().columnMenu?.showResetColumns) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnReset()\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">refresh</cide-ele-icon>\r\n Reset Columns\r\n </button>\r\n }\r\n\r\n <!-- Hide Column Option -->\r\n @if (mergedConfig().columnMenu?.showHideColumn && column.hideable !== false) {\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnHide(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">visibility_off</cide-ele-icon>\r\n Hide Column\r\n </button>\r\n }\r\n\r\n <!-- Aggregation Select Option -->\r\n @if (mergedConfig().columnMenu?.showAggregation && column.aggregatable !== false) {\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n <div class=\"tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase\">Aggregation</div>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'sum')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">functions</cide-ele-icon>\r\n Sum\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'avg')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">analytics</cide-ele-icon>\r\n Average\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'count')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">tag</cide-ele-icon>\r\n Count\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'min')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\r\n Min\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'max')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\r\n Max\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <!-- Table Body -->\r\n <tbody class=\"tw-bg-white tw-divide-y tw-divide-gray-200\">\r\n @if (loading() || isRefreshing() || pageChangeLoading()) {\r\n <!-- Skeleton Loading Rows -->\r\n @for (skeletonItem of getSkeletonArray(); track $index) {\r\n <tr class=\"tw-animate-pulse tw-border-b tw-border-gray-200\">\r\n @for (column of columns; track column.key) {\r\n <td class=\"tw-px-3 tw-py-2 tw-whitespace-nowrap\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width)\r\n ]\">\r\n <div class=\"tw-h-2 tw-bg-gray-200 tw-rounded tw-w-3/4\"></div>\r\n </td>\r\n }\r\n </tr>\r\n }\r\n } @else {\r\n @for (item of displayedData; track trackByFn($index, item)) {\r\n <tr class=\"tw-group hover:tw-bg-gray-50 tw-border-b-2 tw-border-gray-200\"\r\n [ngClass]=\"[\r\n mergedConfig().rowClass || '',\r\n isRefreshing() ? 'tw-opacity-60 tw-pointer-events-none' : '',\r\n isDragDropEnabled() ? 'tw-cursor-move tw-border-2 tw-border-transparent' : '',\r\n !isDragDropEnabled() ? 'tw-transition-colors tw-duration-150' : '',\r\n isTreeEnabled() ? getTreeLevelClass(item) : ''\r\n ]\"\r\n [style.border-color]=\"isDragOverRow === $index ? '#3b82f6' : 'transparent'\"\r\n [style.background-color]=\"isDragOverRow === $index ? '#eff6ff' : ''\"\r\n (click)=\"onRowClick(item)\"\r\n (keydown.enter)=\"onRowClick(item)\"\r\n (keydown.space)=\"onRowClick(item)\"\r\n [class.tw-cursor-pointer]=\"mergedConfig().onRowClick && !isDragDropEnabled()\"\r\n [tabindex]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 0 : -1\"\r\n [attr.role]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'button' : null\"\r\n [attr.aria-label]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'Select row' : null\"\r\n [draggable]=\"isDragDropEnabled()\"\r\n (dragstart)=\"onDragStart($event, item, $index)\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event, item, $index)\"\r\n (dragend)=\"onDragEnd($event)\">\r\n \r\n @for (column of columns; track column.key) {\r\n <td class=\"tw-pr-3 tw-py-1 tw-relative\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width),\r\n mergedConfig().cellClass || '',\r\n column.align === 'center' ? 'tw-text-center' : '',\r\n column.align === 'right' ? 'tw-text-right' : '',\r\n column.truncate !== false ? 'tw-whitespace-nowrap' : 'tw-whitespace-normal'\r\n ]\"\r\n [style.paddingLeft]=\"isTreeEnabled() && $index === 0 ? getTreeIndentStyle(item) : '12px'\"\r\n [style.maxWidth]=\"getColumnMaxWidthClass(column.width) === 'tw-max-w-xs' ? '200px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-sm' ? '300px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-md' ? '400px' : 'none'\"\r\n [style.minWidth]=\"isTreeEnabled() && $index === 0 ? '150px' : '100px'\">\r\n <!-- Tree Expand/Collapse Button (only for first column when tree is enabled) -->\r\n @if (isTreeEnabled() && $index === 0) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <!-- Tree Indentation -->\r\n <div class=\"tw-flex tw-items-center\">\r\n @if (hasChildren(item)) {\r\n <button \r\n variant=\"outline\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, { key: 'toggle-expand', label: 'Toggle', icon: '', variant: 'ghost', onClick: 'toggle-expand' }); $event.stopPropagation()\"\r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-text-gray-500 hover:tw-text-gray-700 tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [title]=\"isItemExpanded(item) ? 'Collapse' : 'Expand'\">\r\n <cide-ele-icon \r\n class=\"tw-w-3 tw-h-3\"\r\n [class.tw-transition-transform]=\"!isDragDropEnabled()\"\r\n [class.tw-rotate-90]=\"isItemExpanded(item)\"\r\n size=\"xs\">\r\n chevron_right\r\n </cide-ele-icon>\r\n </button>\r\n } @else {\r\n <div class=\"tw-w-8 tw-h-5 tw-flex tw-items-center tw-justify-center\">\r\n <!-- <div class=\"tw-w-1 tw-h-1 tw-bg-gray-300 tw-rounded-full\"></div> -->\r\n </div>\r\n }\r\n </div>\r\n \r\n <!-- Cell Content -->\r\n <div class=\"tw-flex-1 tw-w-full\">\r\n @if (column.type === 'text') {\r\n <p class=\"tw-text-sm tw-text-gray-900\"\r\n [class.tw-truncate]=\"column.truncate\"\r\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </p>\r\n } @else if (column.type === 'number') {\r\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'date') {\r\n <span class=\"tw-text-sm tw-text-gray-600\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'boolean') {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\r\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\r\n </span>\r\n } @else if (column.type === 'status' && column.statusConfig) {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\r\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\r\n </span>\r\n } @else if (column.type === 'actions' && column.actions) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n @for (action of column.actions; track action.key) {\r\n <button\r\n cideEleButton\r\n [variant]=\"action.variant || 'ghost'\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\r\n [title]=\"action.tooltip || action.label\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [ngClass]=\"{\r\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\r\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\r\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\r\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\r\n }\">\r\n @if (action.icon) {\r\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\r\n </svg>\r\n }\r\n {{ action.label }}\r\n </button>\r\n }\r\n </div>\r\n } @else if (column.type === 'custom') {\r\n <!-- Template Renderer -->\r\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\r\n <ng-container \r\n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\r\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\r\n </ng-container>\r\n }\r\n <!-- Default rendering -->\r\n @else {\r\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <!-- Regular cell content (non-tree or non-first column) -->\r\n @if (column.type === 'text') {\r\n <p class=\"tw-text-sm tw-text-gray-900\"\r\n [class.tw-truncate]=\"column.truncate\"\r\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </p>\r\n } @else if (column.type === 'number') {\r\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'date') {\r\n <span class=\"tw-text-sm tw-text-gray-600\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'boolean') {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\r\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\r\n </span>\r\n } @else if (column.type === 'status' && column.statusConfig) {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\r\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\r\n </span>\r\n } @else if (column.type === 'actions' && column.actions) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n @for (action of column.actions; track action.key) {\r\n <button\r\n cideEleButton\r\n [variant]=\"action.variant || 'ghost'\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\r\n [title]=\"action.tooltip || action.label\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [ngClass]=\"{\r\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\r\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\r\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\r\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\r\n }\">\r\n @if (action.icon) {\r\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\r\n </svg>\r\n }\r\n {{ action.label }}\r\n </button>\r\n }\r\n </div>\r\n } @else if (column.type === 'custom') {\r\n <!-- Template Renderer -->\r\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\r\n <ng-container \r\n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\r\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\r\n </ng-container>\r\n }\r\n <!-- Default rendering -->\r\n @else {\r\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n \r\n <!-- Empty State -->\r\n @if (displayedData.length === 0) {\r\n <tr class=\"tw-h-full\">\r\n <td [attr.colspan]=\"columns.length\" class=\"tw-px-6 tw-py-22 tw-text-center tw-h-full tw-align-middle\">\r\n <div class=\"tw-text-gray-500 tw-flex tw-flex-col tw-items-center tw-justify-center tw-min-h-[300px]\">\r\n <svg class=\"tw-mx-auto tw-h-12 tw-w-12 tw-text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path>\r\n </svg>\r\n <h3 class=\"tw-mt-2 tw-text-sm tw-font-medium tw-text-gray-900\">No data found</h3>\r\n <p class=\"tw-mt-1 tw-text-sm tw-text-gray-500\">\r\n @if (searchQuery()) {\r\n No results match your search criteria.\r\n } @else {\r\n There are no items to display.\r\n }\r\n </p>\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <!-- Pagination Section -->\r\n @if (paginationConfig.enabled && totalItems() > 0) {\r\n <div class=\"tw-px-3 tw-py-0 tw-border-t tw-border-gray-200 tw-bg-white tw-relative tw-z-20\"\r\n [class.tw-opacity-60]=\"isRefreshing()\">\r\n \r\n <!-- Results Info and Page Size -->\r\n <div class=\"tw-flex tw-flex-col sm:tw-flex-row tw-justify-between tw-items-start sm:tw-items-center tw-space-y-1 sm:tw-space-y-0\">\r\n \r\n <!-- Results Info -->\r\n @if (paginationConfig.showPageInfo) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-3\">\r\n <p class=\"tw-text-sm tw-text-gray-700\">\r\n Showing {{ getItemRangeText() }} results\r\n </p>\r\n \r\n <!-- Page Size Selector -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-500\">view_list</cide-ele-icon>\r\n <span class=\"tw-text-sm tw-text-gray-700\">Per page:</span>\r\n <div class=\"tw-w-16 tw-relative\">\r\n <cide-ele-select\r\n [labelHide]=\"true\"\r\n [ngModel]=\"pageSize()\"\r\n (ngModelChange)=\"updatePageSize($event)\"\r\n [options]=\"getPageSizeOptions()\"\r\n [disabled]=\"isRefreshing()\"\r\n fill=\"outline\"\r\n size=\"xs\"\r\n class=\"tw-z-30\">\r\n </cide-ele-select>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Pagination Controls -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n \r\n <!-- Previous/Next and Page Numbers -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\r\n \r\n <!-- First Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(1)\"\r\n [disabled]=\"currentPage() === 1 || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"First page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">first_page</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Previous Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"previousPage()\"\r\n [disabled]=\"!hasPreviousPage() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Previous page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_left</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Page Numbers -->\r\n @for (page of getEnhancedPageNumbers(); track page) {\r\n @if (page === '...') {\r\n <span class=\"tw-px-2 tw-py-2 tw-text-sm tw-text-gray-500\">...</span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(page)\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n [ngClass]=\"{\r\n 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700': currentPage() === page,\r\n 'tw-bg-white tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-50': currentPage() !== page\r\n }\"\r\n [title]=\"'Page ' + page\">\r\n {{ page }}\r\n </button>\r\n }\r\n }\r\n \r\n <!-- Next Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"nextPage()\"\r\n [disabled]=\"!hasNextPage() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Next page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_right</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Last Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(totalPages())\"\r\n [disabled]=\"currentPage() === totalPages() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Last page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">last_page</cide-ele-icon>\r\n </button>\r\n \r\n </div>\r\n\r\n <!-- Quick Jump and Refresh -->\r\n @if (paginationConfig.showQuickJump || paginationConfig.showRefresh) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-1 tw-border-l tw-border-gray-200 tw-pl-2\">\r\n \r\n <!-- Quick Jump -->\r\n @if (paginationConfig.showQuickJump) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\r\n <span class=\"tw-text-sm tw-text-gray-700\">Go to:</span>\r\n <div class=\"tw-w-16\">\r\n <cide-ele-input id=\"jump-to-page-input\" type=\"number\" [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\"\r\n [(ngModel)]=\"jumpToPage\" [min]=\"1\" [max]=\"totalPages()\"\r\n [disabled]=\"isRefreshing()\"\r\n size=\"xs\"\r\n (keydown.enter)=\"onJumpToPage()\">\r\n </cide-ele-input>\r\n </div>\r\n <button\r\n type=\"button\"\r\n (click)=\"onJumpToPage()\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-bg-gray-100 tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-200 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Go to page\">\r\n Go\r\n </button>\r\n </div>\r\n }\r\n \r\n <!-- Refresh Button -->\r\n @if (paginationConfig.showRefresh) {\r\n <button\r\n type=\"button\"\r\n (click)=\"onRefresh()\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Refresh\">\r\n @if (isRefreshing()) {\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4 tw-animate-spin\">refresh</cide-ele-icon>\r\n } @else {\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">refresh</cide-ele-icon>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n \r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [".data-grid-container{width:100%;display:flex;flex-direction:column;min-height:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:#1f2937;background-color:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.data-grid-container.tw-h-full{height:100%;min-height:100%}.data-grid-container .tw-overflow-x-auto{scrollbar-width:thin;scrollbar-color:#d1d5db #f9fafb}.data-grid-container .tw-overflow-x-auto.tw-flex-1{flex:1;min-height:0}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar{height:6px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-track{background:#f9fafb}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:3px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb:hover{background:#9ca3af}.data-grid-container table{min-height:300px;border-collapse:separate;border-spacing:0;width:100%;background-color:#fff}.data-grid-container thead{background:linear-gradient(180deg,#fafafa,#f7f7f7);border-bottom:1px solid rgba(0,0,0,.06);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.data-grid-container thead th{background:transparent;color:#6b7280;font-weight:500;font-size:12px;text-transform:none;letter-spacing:-.01em;padding:4px 10px;border-bottom:1px solid rgba(0,0,0,.06);text-align:left;white-space:nowrap;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container thead th:first-child{padding-left:12px}.data-grid-container thead th:last-child{padding-right:12px}.data-grid-container thead th:hover{background-color:#00000005}.data-grid-container thead th .column-menu-trigger{opacity:1;transition:all .2s cubic-bezier(.4,0,.2,1);margin-left:4px;cursor:pointer;padding:2px;border-radius:4px}.data-grid-container thead th .column-menu-trigger:hover{background-color:#0000000f}.data-grid-container tbody{background-color:#fff}.data-grid-container tbody td{padding:6px 10px;border-bottom:1px solid rgba(0,0,0,.03);color:#1f2937;font-size:13px;vertical-align:middle;line-height:1.5;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody td:first-child{padding-left:12px}.data-grid-container tbody td:last-child{padding-right:12px}.data-grid-container tbody tr{background-color:#fff;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody tr:hover{background-color:#00000005;transform:scale(1.001)}.data-grid-container tbody tr:hover td{border-bottom-color:#0000000a}.data-grid-container tbody tr:active{background-color:#00000008;transform:scale(.999)}.data-grid-container.tw-h-full table,.data-grid-container.tw-h-full tbody{height:100%}.data-grid-container.tw-table-striped tbody tr:nth-child(2n){background-color:#00000004}.data-grid-container.tw-table-striped tbody tr:nth-child(2n):hover{background-color:#00000008;transform:scale(1.001)}.data-grid-container.tw-table-sm thead th{padding:8px 10px;font-size:11px}.data-grid-container.tw-table-sm thead th:first-child{padding-left:16px}.data-grid-container.tw-table-sm thead th:last-child{padding-right:16px}.data-grid-container.tw-table-sm tbody td{padding:8px 10px;font-size:12px;line-height:1.4}.data-grid-container.tw-table-sm tbody td:first-child{padding-left:16px}.data-grid-container.tw-table-sm tbody td:last-child{padding-right:16px}.data-grid-container tbody tr.tw-cursor-pointer:hover{background-color:#e5f3ff}.data-grid-container.loading-overlay{position:relative}.data-grid-container.loading-overlay:after{content:\"\";position:absolute;inset:0;background:#ffffffd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;animation:fadeIn .2s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.data-grid-container .tw-animate-pulse div{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.data-grid-container .data-grid-action-buttons button{position:relative;border:1px solid rgba(0,0,0,.08);background:transparent;outline:none;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:6px;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button cide-ele-icon{color:#6b7280;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled){background-color:#0000000a;border-color:#0000001f}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled) cide-ele-icon{color:#374151;transform:scale(1.05)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled){background-color:#00000014;border-color:#00000026;transform:scale(.95)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled) cide-ele-icon{transform:scale(.98)}.data-grid-container .data-grid-action-buttons button:focus-visible{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f61f}.data-grid-container .data-grid-action-buttons button:disabled{cursor:not-allowed;opacity:.3;background:transparent!important;border-color:#0000000d}.data-grid-container .action-buttons{display:flex;gap:.25rem}.data-grid-container .action-buttons button{transition:all .15s ease-in-out}.data-grid-container .action-buttons button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.data-grid-container .action-buttons button:disabled{cursor:not-allowed;opacity:.5}.data-grid-container .pagination-controls{padding:12px 20px;border-top:1px solid rgba(0,0,0,.06);background:linear-gradient(180deg,#fafafa,#f7f7f7)}.data-grid-container .pagination-controls button{transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:6px}.data-grid-container .pagination-controls button:hover:not(:disabled){background-color:#0000000a;transform:scale(1.02)}.data-grid-container .pagination-controls button:active:not(:disabled){transform:scale(.98);background-color:#00000014}.data-grid-container .pagination-controls button:disabled{cursor:not-allowed;opacity:.3}.data-grid-container .pagination-controls button.active{background-color:#3b82f61a;color:#3b82f6;font-weight:500;box-shadow:0 0 0 1px #3b82f633}.data-grid-container .pagination-controls input[type=number]{transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(0,0,0,.06);border-radius:6px}.data-grid-container .pagination-controls input[type=number]:hover{border-color:#0000001a}.data-grid-container .pagination-controls input[type=number]:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .status-badge{font-weight:500;letter-spacing:-.01em;padding:4px 12px;border-radius:12px;font-size:12px;display:inline-flex;align-items:center;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .status-badge.active{background-color:#34d39926;color:#059669;border:1px solid rgba(52,211,153,.3)}.data-grid-container .status-badge.active:hover{background-color:#34d39933}.data-grid-container .status-badge.inactive{background-color:#f8717126;color:#dc2626;border:1px solid rgba(248,113,113,.3)}.data-grid-container .status-badge.inactive:hover{background-color:#f8717133}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper{transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:hover:not(:has(input:disabled)){background-color:#00000005}.data-grid-container .data-grid-search-input ::ng-deep input{font-size:13px;font-weight:400;color:#1f2937;transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep input::placeholder{color:#9ca3af;font-weight:400}.data-grid-container .data-grid-search-input ::ng-deep input:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep input:disabled{background-color:#00000005;color:#9ca3af;cursor:not-allowed}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-leading-icon cide-ele-icon{color:#9ca3af;transition:color .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep input:focus~.cide-input-leading-icon cide-ele-icon,.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:has(input:focus) .cide-input-leading-icon cide-ele-icon{color:#6b7280}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]{border:1px solid #e5e7eb;background-color:#fafafa;border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:hover:not(:has(input:disabled)){border-color:#d1d5db;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:has(input:focus){border-color:#3b82f64d;background-color:#fff}.data-grid-container .search-input{position:relative}.data-grid-container .search-input input{transition:all .15s ease-in-out}.data-grid-container .search-input input:focus{box-shadow:0 0 0 3px #3b82f61a}.data-grid-container .search-input .search-icon{pointer-events:none}.data-grid-container .column-menu-dropdown{animation:dropdownFadeIn .15s cubic-bezier(.4,0,.2,1);box-shadow:0 10px 25px #0000001a,0 4px 10px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(0,0,0,.05)}.data-grid-container .column-menu-dropdown button{font-size:13px;font-weight:400;letter-spacing:-.01em;text-align:left;transition:all .15s cubic-bezier(.4,0,.2,1)}.data-grid-container .column-menu-dropdown button:hover{background-color:#3b82f60d;color:#1f2937}.data-grid-container .column-menu-dropdown button:hover cide-ele-icon{color:#3b82f6}.data-grid-container .column-menu-dropdown button:active{background-color:#3b82f61a;transform:scale(.98)}.data-grid-container .column-menu-dropdown .tw-uppercase{letter-spacing:.05em}@keyframes dropdownFadeIn{0%{opacity:0;transform:translateY(-8px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.data-grid-container .empty-state{padding:4rem 2rem;text-align:center}.data-grid-container .empty-state svg{margin:0 auto 1.5rem;opacity:.3;transition:all .3s cubic-bezier(.4,0,.2,1)}.data-grid-container .empty-state svg:hover{opacity:.5;transform:scale(1.05)}.data-grid-container .empty-state h3{margin-bottom:.75rem;font-weight:600;color:#374151;font-size:16px;letter-spacing:-.01em}.data-grid-container .empty-state p{color:#6b7280;font-size:14px;line-height:1.5}.data-grid-container.tw-h-full tbody tr:only-child td{height:100%;vertical-align:middle}.data-grid-container.tw-h-full table.empty-table{height:100%}.data-grid-container.tw-h-full tbody tr:only-child{height:100%}@media (max-width: 640px){.data-grid-container .tw-px-6{padding-left:1rem;padding-right:1rem}.data-grid-container .pagination-controls{flex-direction:column;gap:1rem}.data-grid-container .pagination-controls .flex{justify-content:center}.data-grid-container .pagination-info{display:none}.data-grid-container .search-actions{flex-direction:column;gap:1rem}}@media (prefers-color-scheme: dark){.data-grid-container.dark-mode{background-color:#1f2937;color:#f9fafb}.data-grid-container.dark-mode table{background-color:#1f2937}.data-grid-container.dark-mode thead{background-color:#374151}.data-grid-container.dark-mode tbody tr{border-color:#374151}.data-grid-container.dark-mode tbody tr:hover{background-color:#374151}.data-grid-container.dark-mode .tw-text-gray-900{color:#f9fafb!important}.data-grid-container.dark-mode .tw-text-gray-600{color:#d1d5db!important}.data-grid-container.dark-mode .tw-text-gray-500{color:#9ca3af!important}}.data-grid-container.drag-drop-enabled tbody tr{transition:all .2s ease}.data-grid-container.drag-drop-enabled tbody tr:hover{background-color:#f3f4f6}.data-grid-container.drag-drop-enabled tbody tr.tw-opacity-50{background-color:#dbeafecc;border:2px dashed #3b82f6;border-radius:4px}.data-grid-container.drag-drop-enabled tbody tr.tw-bg-blue-50{background-color:#eff6ffe6}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]{cursor:move;position:relative}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:hover{background-color:#f8fafc;box-shadow:0 1px 3px #0000001a}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:active{cursor:grabbing}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]{position:relative}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]:before{content:\"\";position:absolute;top:-2px;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);z-index:10}.data-grid-container.tree-enabled tbody tr td:first-child{position:relative}.data-grid-container.tree-enabled tbody tr td:first-child button{transition:all .15s ease}.data-grid-container.tree-enabled tbody tr td:first-child button:hover{background-color:#f3f4f6;border-radius:2px}.data-grid-container.tree-enabled tbody tr td:first-child button svg{transition:transform .2s ease}.data-grid-container.tree-enabled tbody tr[style*=padding-left]{border-left:2px solid transparent}.data-grid-container.tree-enabled tbody tr[style*=padding-left]:hover{border-left-color:#e5e7eb}.tree-level-0{border-bottom:2px solid #e5e7eb!important;background-color:#fff}.tree-level-0:hover{background-color:#f5f5f5!important}.tree-level-0 td:first-child{font-weight:600}.tree-level-1{border-bottom:1px solid #d1d5db!important;background-color:#fafafa}.tree-level-1:hover{background-color:#f1f5f9!important}.tree-level-1 td:first-child{font-weight:500}.tree-level-2{border-bottom:1px solid #d1d5db!important;background-color:#f9fafb}.tree-level-2:hover{background-color:#f3f4f6!important}.tree-level-2 td:first-child{font-weight:400}.tree-level-deep{border-bottom:1px solid #e5e7eb!important;background-color:#fefefe}.tree-level-deep:hover{background-color:#f9fafb!important}.tree-level-deep td:first-child{font-weight:300}table td{box-sizing:border-box;word-wrap:break-word;overflow-wrap:break-word}table td.tw-overflow-hidden{overflow:hidden;text-overflow:ellipsis}table td:first-child{min-width:150px}table td:first-child>div{display:flex;align-items:center;min-width:0}table td:first-child>div .tw-flex{min-width:0}table td .tw-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: CideInputComponent, selector: "cide-ele-input", inputs: ["fill", "label", "labelHide", "disabled", "clearInput", "labelPlacement", "labelDir", "placeholder", "leadingIcon", "trailingIcon", "helperText", "helperTextCollapse", "hideHelperAndErrorText", "errorText", "maxlength", "minlength", "required", "autocapitalize", "autocomplete", "type", "width", "id", "ngModel", "option", "min", "max", "size"], outputs: ["ngModelChange"] }, { kind: "component", type: CideSelectComponent, selector: "cide-ele-select", inputs: ["label", "labelHide", "placeholder", "helperText", "errorText", "required", "disabled", "id", "ngModel", "size", "fill", "labelPlacement", "labelDir", "leadingIcon", "trailingIcon", "clearInput", "options", "multiple", "searchable", "showSearchInput", "loading", "valueKey", "labelKey"], outputs: ["ngModelChange", "change", "searchChange"] }, { kind: "component", type: CideIconComponent, selector: "cide-ele-icon", inputs: ["size", "type", "toolTip"] }, { kind: "component", type: CideEleButtonComponent, selector: "button[cideEleButton], a[cideEleButton]", inputs: ["label", "variant", "size", "type", "shape", "elevation", "disabled", "id", "loading", "fullWidth", "leftIcon", "rightIcon", "customClass", "tooltip", "ariaLabel", "testId", "routerLink", "routerExtras", "preventDoubleClick", "animated"], outputs: ["btnClick", "doubleClick"] }] });
|
|
10407
10396
|
}
|
|
10408
10397
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImport: i0, type: CideEleDataGridComponent, decorators: [{
|
|
10409
10398
|
type: Component,
|
|
@@ -10415,7 +10404,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.7", ngImpor
|
|
|
10415
10404
|
CideSelectComponent,
|
|
10416
10405
|
CideIconComponent,
|
|
10417
10406
|
CideEleButtonComponent
|
|
10418
|
-
], template: " <!-- Data Grid Component -->\r\n <div class=\"data-grid-container tw-bg-white tw-shadow tw-rounded-lg tw-overflow-visible tw-flex tw-flex-col\" \r\n [ngClass]=\"[\r\n mergedConfig().tableClass || '',\r\n mergedConfig().fullHeight ? 'tw-h-full' : '',\r\n isDragDropEnabled() ? 'drag-drop-enabled' : '',\r\n isTreeEnabled() ? 'tree-enabled' : ''\r\n ]\">\r\n \r\n <!-- Header Section -->\r\n @if (mergedConfig().title || mergedConfig().subtitle) {\r\n <div class=\"tw-px-3 tw-py-2 tw-border-b tw-border-gray-200\">\r\n @if (mergedConfig().title) {\r\n <h3 class=\"tw-text-base tw-font-semibold tw-text-gray-900\">\r\n {{ mergedConfig().title }}\r\n </h3>\r\n }\r\n @if (mergedConfig().subtitle) {\r\n <p class=\"tw-text-sm tw-text-gray-600 tw-mt-0.5\">\r\n {{ mergedConfig().subtitle }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Search Section -->\r\n @if (searchConfig.enabled) {\r\n <div class=\"tw-px-3 tw-py-1.5 tw-border-b tw-border-gray-200\">\r\n <div class=\"tw-flex tw-items-center tw-justify-between\">\r\n <!-- Left Side: Search Input and Action Icons -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1.5\">\r\n <!-- Search Input - Apple Style -->\r\n <div class=\"tw-max-w-md data-grid-search-input\">\r\n <cide-ele-input [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\" id=\"search-input\" type=\"text\"\r\n [ngModel]=\"searchQuery()\"\r\n (ngModelChange)=\"updateSearchQuery($event)\"\r\n [placeholder]=\"searchConfig.placeholder\"\r\n [disabled]=\"loading() || isRefreshing()\"\r\n leadingIcon=\"search\"\r\n fill=\"outline\"\r\n size=\"sm\">\r\n </cide-ele-input>\r\n </div>\r\n \r\n <!-- Action Icons (Filter, Sort, Download) - Apple Style Compact -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1 data-grid-action-buttons\">\r\n <!-- Filter Button -->\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Filter\"\r\n (click)=\"onActionClick(null, { key: 'filter', label: 'Filter', icon: 'filter_list', variant: 'ghost', onClick: 'filter' })\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">filter_list</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Sort Button -->\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Sort\"\r\n (click)=\"onActionClick(null, { key: 'sort', label: 'Sort', icon: 'swap_vert', variant: 'ghost', onClick: 'sort' })\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">swap_vert</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Download/Export Button with Dropdown -->\r\n <div class=\"tw-relative\">\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Export\"\r\n (click)=\"toggleExportMenu($event)\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">file_download</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Export Dropdown Menu -->\r\n @if (showExportMenu()) {\r\n <div class=\"tw-absolute tw-right-0 tw-mt-1 tw-w-48 tw-bg-white tw-rounded-md tw-shadow-lg tw-border tw-border-gray-200 tw-z-50\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"tw-py-1\">\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100 tw-flex tw-items-center\"\r\n (click)=\"exportData('csv')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-2 tw-text-green-600\">description</cide-ele-icon>\r\n Export as CSV\r\n </button>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100 tw-flex tw-items-center\"\r\n (click)=\"exportData('excel')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-2 tw-text-blue-600\">file_present</cide-ele-icon>\r\n Export as Excel\r\n </button>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-100 tw-flex tw-items-center\"\r\n (click)=\"exportData('pdf')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-2 tw-text-red-600\">picture_as_pdf</cide-ele-icon>\r\n Export as PDF\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- Right Side: Drag Order Actions -->\r\n @if (isDragDropEnabled() && (isDragging() || hasOrderChanged())) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <button cideEleButton \r\n variant=\"outline\" \r\n size=\"sm\" \r\n type=\"button\"\r\n (click)=\"onActionClick(null, { key: 'reset-order', label: 'Reset Order', icon: 'undo', variant: 'outline', onClick: 'resetOrder' })\"\r\n class=\"tw-text-blue-700 tw-border-blue-300 hover:tw-bg-blue-100\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">undo</cide-ele-icon>\r\n Reset Order\r\n </button>\r\n <button cideEleButton \r\n variant=\"primary\" \r\n size=\"sm\" \r\n type=\"button\"\r\n (click)=\"onActionClick(null, { key: 'save-order', label: 'Save Order', icon: 'save', variant: 'primary', onClick: 'saveOrder' })\"\r\n class=\"tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">save</cide-ele-icon>\r\n Save Order\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Table Section -->\r\n <div class=\"tw-overflow-x-auto tw-relative\"\r\n [ngClass]=\"{\r\n 'tw-flex-1 tw-min-h-0': mergedConfig().fullHeight,\r\n 'tw-overflow-y-auto': scrollConfig?.enabled,\r\n 'tw-max-h-full': scrollConfig?.enabled\r\n }\"\r\n [style.maxHeight]=\"scrollConfig?.enabled ? scrollConfig?.maxHeight : null\"\r\n [style.minHeight]=\"scrollConfig?.enabled ? scrollConfig?.minHeight : null\">\r\n <table class=\"tw-min-w-full tw-divide-y tw-divide-gray-200 tw-h-full tw-table-fixed\"\r\n [class.empty-table]=\"displayedData.length === 0\"\r\n [ngClass]=\"{\r\n 'tw-table-striped': mergedConfig().striped,\r\n 'tw-border': mergedConfig().bordered,\r\n 'tw-table-sm': mergedConfig().compact\r\n }\"\r\n style=\"table-layout: fixed;\">\r\n \r\n <!-- Table Header -->\r\n <thead class=\"tw-bg-gray-50\" \r\n [ngClass]=\"[\r\n mergedConfig().headerClass || '',\r\n scrollConfig?.enabled && scrollConfig?.stickyHeader ? 'tw-sticky tw-top-0 tw-z-10' : ''\r\n ]\">\r\n <tr>\r\n @for (column of visibleColumns(); track column.key) {\r\n <th\r\n class=\"tw-px-3 tw-py-2 tw-text-left tw-text-xs tw-font-medium tw-text-gray-500 tw-uppercase tw-tracking-wider tw-relative\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width),\r\n column.align === 'center' ? 'tw-text-center' : '',\r\n column.align === 'right' ? 'tw-text-right' : ''\r\n ]\"\r\n [title]=\"column.header\">\r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-gap-1\">\r\n <span class=\"tw-truncate tw-flex-1\">{{ column.header }}</span>\r\n \r\n <!-- Active Filter Indicator -->\r\n @if (isColumnFiltered(column.key)) {\r\n <div class=\"tw-inline-flex tw-items-center tw-px-1.5 tw-py-0.5 tw-rounded tw-bg-blue-100 tw-text-blue-700 tw-text-xs tw-font-medium tw-mr-1\">\r\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-mr-0.5\">filter_alt</cide-ele-icon>\r\n {{ getActiveFilterCount(column.key) }}\r\n </div>\r\n }\r\n \r\n <!-- Column Menu Trigger (Three Dots Icon) -->\r\n @if (mergedConfig().columnMenu?.enabled) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-600 hover:tw-text-gray-800 hover:tw-bg-gray-100 tw-transition-all column-menu-trigger\"\r\n [class.tw-text-blue-600]=\"isColumnMenuOpen(column.key) || isColumnFiltered(column.key)\"\r\n [class.tw-bg-blue-50]=\"isColumnFiltered(column.key)\"\r\n (click)=\"toggleColumnMenu(column.key, $event)\"\r\n title=\"Column options\">\r\n <cide-ele-icon class=\"tw-w-5 tw-h-4\">more_vert</cide-ele-icon>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Column Menu Dropdown -->\r\n @if (isColumnMenuOpen(column.key)) {\r\n <div class=\"column-menu-dropdown tw-absolute tw-z-50 tw-mt-2 tw-w-56 tw-rounded-lg tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5\">\r\n <div class=\"tw-py-1\">\r\n <!-- Sort Options -->\r\n @if (mergedConfig().columnMenu?.showSort && column.sortable !== false) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnSort(column, 'asc')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\r\n Ascending\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnSort(column, 'desc')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\r\n Descending\r\n </button>\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n }\r\n\r\n <!-- Filter Option -->\r\n @if (mergedConfig().columnMenu?.showFilter && column.filterable !== false) {\r\n <!-- Check if there's a custom filter renderer template -->\r\n @if (column.filterRenderer && templateRenderers[column.filterRenderer]) {\r\n <div class=\"tw-px-4 tw-py-2\">\r\n <ng-container [ngTemplateOutlet]=\"$any(templateRenderers[column.filterRenderer])\"\r\n [ngTemplateOutletContext]=\"{ $implicit: column, column: column, onFilter: onColumnFilter.bind(this) }\">\r\n </ng-container>\r\n </div>\r\n } @else {\r\n <!-- Excel-style Filter Button -->\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n [class.tw-bg-blue-50]=\"isFilterPanelOpen(column.key)\"\r\n (click)=\"toggleFilterPanel(column.key, $event)\">\r\n <div class=\"tw-flex tw-items-center\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">filter_list</cide-ele-icon>\r\n Filter\r\n </div>\r\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-gray-400\">\r\n {{ isFilterPanelOpen(column.key) ? 'expand_less' : 'expand_more' }}\r\n </cide-ele-icon>\r\n </button>\r\n \r\n <!-- Excel-style Filter Panel -->\r\n @if (isFilterPanelOpen(column.key)) {\r\n <div class=\"tw-px-2 tw-py-2 tw-bg-gray-50\" (click)=\"$event.stopPropagation()\">\r\n <!-- Search box -->\r\n <div class=\"tw-px-2 tw-mb-2\">\r\n <input\r\n type=\"text\"\r\n class=\"tw-w-full tw-px-2 tw-py-1 tw-text-xs tw-border tw-border-gray-300 tw-rounded focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500\"\r\n placeholder=\"Search...\"\r\n [(ngModel)]=\"filterSearchTerm\"\r\n (click)=\"$event.stopPropagation()\">\r\n </div>\r\n \r\n <!-- Select All / Deselect All -->\r\n <div class=\"tw-px-2 tw-mb-1 tw-flex tw-gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\r\n (click)=\"selectAllFilterValues(column); $event.stopPropagation()\">\r\n Select All\r\n </button>\r\n <span class=\"tw-text-xs tw-text-gray-400\">|</span>\r\n <button\r\n type=\"button\"\r\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\r\n (click)=\"deselectAllFilterValues(column); $event.stopPropagation()\">\r\n Clear\r\n </button>\r\n </div>\r\n \r\n <!-- Filter values list -->\r\n <div class=\"tw-max-h-48 tw-overflow-y-auto\">\r\n @for (item of getUniqueColumnValues(column); track item.value) {\r\n <label class=\"tw-flex tw-items-center tw-px-2 tw-py-1 tw-text-xs hover:tw-bg-white tw-cursor-pointer tw-rounded\">\r\n <input\r\n type=\"checkbox\"\r\n class=\"tw-mr-2 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\r\n [checked]=\"item.checked\"\r\n (change)=\"toggleFilterValue(column, item.value, $any($event.target).checked)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <span class=\"tw-flex-1 tw-truncate\">{{ item.label }}</span>\r\n <span class=\"tw-text-gray-400 tw-ml-1\">({{ item.count }})</span>\r\n </label>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n }\r\n\r\n <!-- Autosize Option -->\r\n @if (mergedConfig().columnMenu?.showAutosize) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAutosize(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">fit_screen</cide-ele-icon>\r\n Autosize\r\n </button>\r\n }\r\n\r\n <!-- Group By Column Option -->\r\n @if (mergedConfig().columnMenu?.showGroupBy && column.groupable !== false) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnGroupBy(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">group_work</cide-ele-icon>\r\n Group By Column\r\n </button>\r\n }\r\n\r\n <!-- Manage Columns Option -->\r\n @if (mergedConfig().columnMenu?.showManageColumns) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onManageColumns()\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">view_column</cide-ele-icon>\r\n Manage Columns\r\n </button>\r\n }\r\n\r\n <!-- Reset Columns Option -->\r\n @if (mergedConfig().columnMenu?.showResetColumns) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnReset()\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">refresh</cide-ele-icon>\r\n Reset Columns\r\n </button>\r\n }\r\n\r\n <!-- Hide Column Option -->\r\n @if (mergedConfig().columnMenu?.showHideColumn && column.hideable !== false) {\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnHide(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">visibility_off</cide-ele-icon>\r\n Hide Column\r\n </button>\r\n }\r\n\r\n <!-- Aggregation Select Option -->\r\n @if (mergedConfig().columnMenu?.showAggregation && column.aggregatable !== false) {\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n <div class=\"tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase\">Aggregation</div>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'sum')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">functions</cide-ele-icon>\r\n Sum\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'avg')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">analytics</cide-ele-icon>\r\n Average\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'count')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">tag</cide-ele-icon>\r\n Count\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'min')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\r\n Min\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'max')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\r\n Max\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <!-- Table Body -->\r\n <tbody class=\"tw-bg-white tw-divide-y tw-divide-gray-200\">\r\n @if (loading() || isRefreshing() || pageChangeLoading()) {\r\n <!-- Skeleton Loading Rows -->\r\n @for (skeletonItem of getSkeletonArray(); track $index) {\r\n <tr class=\"tw-animate-pulse tw-border-b tw-border-gray-200\">\r\n @for (column of columns; track column.key) {\r\n <td class=\"tw-px-3 tw-py-2 tw-whitespace-nowrap\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width)\r\n ]\">\r\n <div class=\"tw-h-2 tw-bg-gray-200 tw-rounded tw-w-3/4\"></div>\r\n </td>\r\n }\r\n </tr>\r\n }\r\n } @else {\r\n @for (item of displayedData; track trackByFn($index, item)) {\r\n <tr class=\"tw-group hover:tw-bg-gray-50 tw-border-b-2 tw-border-gray-200\"\r\n [ngClass]=\"[\r\n mergedConfig().rowClass || '',\r\n isRefreshing() ? 'tw-opacity-60 tw-pointer-events-none' : '',\r\n isDragDropEnabled() ? 'tw-cursor-move tw-border-2 tw-border-transparent' : '',\r\n !isDragDropEnabled() ? 'tw-transition-colors tw-duration-150' : '',\r\n isTreeEnabled() ? getTreeLevelClass(item) : ''\r\n ]\"\r\n [style.border-color]=\"isDragOverRow === $index ? '#3b82f6' : 'transparent'\"\r\n [style.background-color]=\"isDragOverRow === $index ? '#eff6ff' : ''\"\r\n (click)=\"onRowClick(item)\"\r\n (keydown.enter)=\"onRowClick(item)\"\r\n (keydown.space)=\"onRowClick(item)\"\r\n [class.tw-cursor-pointer]=\"mergedConfig().onRowClick && !isDragDropEnabled()\"\r\n [tabindex]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 0 : -1\"\r\n [attr.role]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'button' : null\"\r\n [attr.aria-label]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'Select row' : null\"\r\n [draggable]=\"isDragDropEnabled()\"\r\n (dragstart)=\"onDragStart($event, item, $index)\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event, item, $index)\"\r\n (dragend)=\"onDragEnd($event)\">\r\n \r\n @for (column of columns; track column.key) {\r\n <td class=\"tw-pr-3 tw-py-1 tw-relative\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width),\r\n mergedConfig().cellClass || '',\r\n column.align === 'center' ? 'tw-text-center' : '',\r\n column.align === 'right' ? 'tw-text-right' : '',\r\n column.truncate !== false ? 'tw-whitespace-nowrap' : 'tw-whitespace-normal'\r\n ]\"\r\n [style.paddingLeft]=\"isTreeEnabled() && $index === 0 ? getTreeIndentStyle(item) : '12px'\"\r\n [style.maxWidth]=\"getColumnMaxWidthClass(column.width) === 'tw-max-w-xs' ? '200px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-sm' ? '300px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-md' ? '400px' : 'none'\"\r\n [style.minWidth]=\"isTreeEnabled() && $index === 0 ? '150px' : '100px'\">\r\n <!-- Tree Expand/Collapse Button (only for first column when tree is enabled) -->\r\n @if (isTreeEnabled() && $index === 0) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <!-- Tree Indentation -->\r\n <div class=\"tw-flex tw-items-center\">\r\n @if (hasChildren(item)) {\r\n <button \r\n variant=\"outline\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, { key: 'toggle-expand', label: 'Toggle', icon: '', variant: 'ghost', onClick: 'toggle-expand' }); $event.stopPropagation()\"\r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-text-gray-500 hover:tw-text-gray-700 tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [title]=\"isItemExpanded(item) ? 'Collapse' : 'Expand'\">\r\n <cide-ele-icon \r\n class=\"tw-w-3 tw-h-3\"\r\n [class.tw-transition-transform]=\"!isDragDropEnabled()\"\r\n [class.tw-rotate-90]=\"isItemExpanded(item)\"\r\n size=\"xs\">\r\n chevron_right\r\n </cide-ele-icon>\r\n </button>\r\n } @else {\r\n <div class=\"tw-w-8 tw-h-5 tw-flex tw-items-center tw-justify-center\">\r\n <!-- <div class=\"tw-w-1 tw-h-1 tw-bg-gray-300 tw-rounded-full\"></div> -->\r\n </div>\r\n }\r\n </div>\r\n \r\n <!-- Cell Content -->\r\n <div class=\"tw-flex-1 tw-w-full\">\r\n @if (column.type === 'text') {\r\n <p class=\"tw-text-sm tw-text-gray-900\"\r\n [class.tw-truncate]=\"column.truncate\"\r\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </p>\r\n } @else if (column.type === 'number') {\r\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'date') {\r\n <span class=\"tw-text-sm tw-text-gray-600\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'boolean') {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\r\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\r\n </span>\r\n } @else if (column.type === 'status' && column.statusConfig) {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\r\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\r\n </span>\r\n } @else if (column.type === 'actions' && column.actions) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n @for (action of column.actions; track action.key) {\r\n <button\r\n cideEleButton\r\n [variant]=\"action.variant || 'ghost'\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\r\n [title]=\"action.tooltip || action.label\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [ngClass]=\"{\r\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\r\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\r\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\r\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\r\n }\">\r\n @if (action.icon) {\r\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\r\n </svg>\r\n }\r\n {{ action.label }}\r\n </button>\r\n }\r\n </div>\r\n } @else if (column.type === 'custom') {\r\n <!-- Template Renderer -->\r\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\r\n <ng-container \r\n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\r\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\r\n </ng-container>\r\n }\r\n <!-- Default rendering -->\r\n @else {\r\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <!-- Regular cell content (non-tree or non-first column) -->\r\n @if (column.type === 'text') {\r\n <p class=\"tw-text-sm tw-text-gray-900\"\r\n [class.tw-truncate]=\"column.truncate\"\r\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </p>\r\n } @else if (column.type === 'number') {\r\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'date') {\r\n <span class=\"tw-text-sm tw-text-gray-600\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'boolean') {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\r\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\r\n </span>\r\n } @else if (column.type === 'status' && column.statusConfig) {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\r\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\r\n </span>\r\n } @else if (column.type === 'actions' && column.actions) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n @for (action of column.actions; track action.key) {\r\n <button\r\n cideEleButton\r\n [variant]=\"action.variant || 'ghost'\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\r\n [title]=\"action.tooltip || action.label\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [ngClass]=\"{\r\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\r\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\r\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\r\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\r\n }\">\r\n @if (action.icon) {\r\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\r\n </svg>\r\n }\r\n {{ action.label }}\r\n </button>\r\n }\r\n </div>\r\n } @else if (column.type === 'custom') {\r\n <!-- Template Renderer -->\r\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\r\n <ng-container \r\n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\r\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\r\n </ng-container>\r\n }\r\n <!-- Default rendering -->\r\n @else {\r\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n \r\n <!-- Empty State -->\r\n @if (displayedData.length === 0) {\r\n <tr class=\"tw-h-full\">\r\n <td [attr.colspan]=\"columns.length\" class=\"tw-px-6 tw-py-22 tw-text-center tw-h-full tw-align-middle\">\r\n <div class=\"tw-text-gray-500 tw-flex tw-flex-col tw-items-center tw-justify-center tw-min-h-[300px]\">\r\n <svg class=\"tw-mx-auto tw-h-12 tw-w-12 tw-text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path>\r\n </svg>\r\n <h3 class=\"tw-mt-2 tw-text-sm tw-font-medium tw-text-gray-900\">No data found</h3>\r\n <p class=\"tw-mt-1 tw-text-sm tw-text-gray-500\">\r\n @if (searchQuery()) {\r\n No results match your search criteria.\r\n } @else {\r\n There are no items to display.\r\n }\r\n </p>\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <!-- Pagination Section -->\r\n @if (paginationConfig.enabled && totalItems() > 0) {\r\n <div class=\"tw-px-3 tw-py-0 tw-border-t tw-border-gray-200 tw-bg-white tw-relative tw-z-20\"\r\n [class.tw-opacity-60]=\"isRefreshing()\">\r\n \r\n <!-- Results Info and Page Size -->\r\n <div class=\"tw-flex tw-flex-col sm:tw-flex-row tw-justify-between tw-items-start sm:tw-items-center tw-space-y-1 sm:tw-space-y-0\">\r\n \r\n <!-- Results Info -->\r\n @if (paginationConfig.showPageInfo) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-3\">\r\n <p class=\"tw-text-sm tw-text-gray-700\">\r\n Showing {{ getItemRangeText() }} results\r\n </p>\r\n \r\n <!-- Page Size Selector -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-500\">view_list</cide-ele-icon>\r\n <span class=\"tw-text-sm tw-text-gray-700\">Per page:</span>\r\n <div class=\"tw-w-16 tw-relative\">\r\n <cide-ele-select\r\n [labelHide]=\"true\"\r\n [ngModel]=\"pageSize()\"\r\n (ngModelChange)=\"updatePageSize($event)\"\r\n [options]=\"getPageSizeOptions()\"\r\n [disabled]=\"isRefreshing()\"\r\n fill=\"outline\"\r\n size=\"xs\"\r\n class=\"tw-z-30\">\r\n </cide-ele-select>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Pagination Controls -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n \r\n <!-- Previous/Next and Page Numbers -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\r\n \r\n <!-- First Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(1)\"\r\n [disabled]=\"currentPage() === 1 || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"First page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">first_page</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Previous Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"previousPage()\"\r\n [disabled]=\"!hasPreviousPage() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Previous page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_left</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Page Numbers -->\r\n @for (page of getEnhancedPageNumbers(); track page) {\r\n @if (page === '...') {\r\n <span class=\"tw-px-2 tw-py-2 tw-text-sm tw-text-gray-500\">...</span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(page)\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n [ngClass]=\"{\r\n 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700': currentPage() === page,\r\n 'tw-bg-white tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-50': currentPage() !== page\r\n }\"\r\n [title]=\"'Page ' + page\">\r\n {{ page }}\r\n </button>\r\n }\r\n }\r\n \r\n <!-- Next Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"nextPage()\"\r\n [disabled]=\"!hasNextPage() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Next page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_right</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Last Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(totalPages())\"\r\n [disabled]=\"currentPage() === totalPages() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Last page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">last_page</cide-ele-icon>\r\n </button>\r\n \r\n </div>\r\n\r\n <!-- Quick Jump and Refresh -->\r\n @if (paginationConfig.showQuickJump || paginationConfig.showRefresh) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-1 tw-border-l tw-border-gray-200 tw-pl-2\">\r\n \r\n <!-- Quick Jump -->\r\n @if (paginationConfig.showQuickJump) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\r\n <span class=\"tw-text-sm tw-text-gray-700\">Go to:</span>\r\n <div class=\"tw-w-16\">\r\n <cide-ele-input id=\"jump-to-page-input\" type=\"number\" [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\"\r\n [(ngModel)]=\"jumpToPage\" [min]=\"1\" [max]=\"totalPages()\"\r\n [disabled]=\"isRefreshing()\"\r\n size=\"xs\"\r\n (keydown.enter)=\"onJumpToPage()\">\r\n </cide-ele-input>\r\n </div>\r\n <button\r\n type=\"button\"\r\n (click)=\"onJumpToPage()\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-bg-gray-100 tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-200 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Go to page\">\r\n Go\r\n </button>\r\n </div>\r\n }\r\n \r\n <!-- Refresh Button -->\r\n @if (paginationConfig.showRefresh) {\r\n <button\r\n type=\"button\"\r\n (click)=\"onRefresh()\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Refresh\">\r\n @if (isRefreshing()) {\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4 tw-animate-spin\">refresh</cide-ele-icon>\r\n } @else {\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">refresh</cide-ele-icon>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n \r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [".data-grid-container{width:100%;display:flex;flex-direction:column;min-height:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:#1f2937;background-color:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.data-grid-container.tw-h-full{height:100%;min-height:100%}.data-grid-container .tw-overflow-x-auto{scrollbar-width:thin;scrollbar-color:#d1d5db #f9fafb}.data-grid-container .tw-overflow-x-auto.tw-flex-1{flex:1;min-height:0}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar{height:6px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-track{background:#f9fafb}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:3px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb:hover{background:#9ca3af}.data-grid-container table{min-height:300px;border-collapse:separate;border-spacing:0;width:100%;background-color:#fff}.data-grid-container thead{background:linear-gradient(180deg,#fafafa,#f7f7f7);border-bottom:1px solid rgba(0,0,0,.06);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.data-grid-container thead th{background:transparent;color:#6b7280;font-weight:500;font-size:12px;text-transform:none;letter-spacing:-.01em;padding:4px 10px;border-bottom:1px solid rgba(0,0,0,.06);text-align:left;white-space:nowrap;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container thead th:first-child{padding-left:12px}.data-grid-container thead th:last-child{padding-right:12px}.data-grid-container thead th:hover{background-color:#00000005}.data-grid-container thead th .column-menu-trigger{opacity:1;transition:all .2s cubic-bezier(.4,0,.2,1);margin-left:4px;cursor:pointer;padding:2px;border-radius:4px}.data-grid-container thead th .column-menu-trigger:hover{background-color:#0000000f}.data-grid-container tbody{background-color:#fff}.data-grid-container tbody td{padding:6px 10px;border-bottom:1px solid rgba(0,0,0,.03);color:#1f2937;font-size:13px;vertical-align:middle;line-height:1.5;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody td:first-child{padding-left:12px}.data-grid-container tbody td:last-child{padding-right:12px}.data-grid-container tbody tr{background-color:#fff;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody tr:hover{background-color:#00000005;transform:scale(1.001)}.data-grid-container tbody tr:hover td{border-bottom-color:#0000000a}.data-grid-container tbody tr:active{background-color:#00000008;transform:scale(.999)}.data-grid-container.tw-h-full table,.data-grid-container.tw-h-full tbody{height:100%}.data-grid-container.tw-table-striped tbody tr:nth-child(2n){background-color:#00000004}.data-grid-container.tw-table-striped tbody tr:nth-child(2n):hover{background-color:#00000008;transform:scale(1.001)}.data-grid-container.tw-table-sm thead th{padding:8px 10px;font-size:11px}.data-grid-container.tw-table-sm thead th:first-child{padding-left:16px}.data-grid-container.tw-table-sm thead th:last-child{padding-right:16px}.data-grid-container.tw-table-sm tbody td{padding:8px 10px;font-size:12px;line-height:1.4}.data-grid-container.tw-table-sm tbody td:first-child{padding-left:16px}.data-grid-container.tw-table-sm tbody td:last-child{padding-right:16px}.data-grid-container tbody tr.tw-cursor-pointer:hover{background-color:#e5f3ff}.data-grid-container.loading-overlay{position:relative}.data-grid-container.loading-overlay:after{content:\"\";position:absolute;inset:0;background:#ffffffd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;animation:fadeIn .2s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.data-grid-container .tw-animate-pulse div{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.data-grid-container .data-grid-action-buttons button{position:relative;border:1px solid rgba(0,0,0,.08);background:transparent;outline:none;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:6px;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button cide-ele-icon{color:#6b7280;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled){background-color:#0000000a;border-color:#0000001f}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled) cide-ele-icon{color:#374151;transform:scale(1.05)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled){background-color:#00000014;border-color:#00000026;transform:scale(.95)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled) cide-ele-icon{transform:scale(.98)}.data-grid-container .data-grid-action-buttons button:focus-visible{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f61f}.data-grid-container .data-grid-action-buttons button:disabled{cursor:not-allowed;opacity:.3;background:transparent!important;border-color:#0000000d}.data-grid-container .action-buttons{display:flex;gap:.25rem}.data-grid-container .action-buttons button{transition:all .15s ease-in-out}.data-grid-container .action-buttons button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.data-grid-container .action-buttons button:disabled{cursor:not-allowed;opacity:.5}.data-grid-container .pagination-controls{padding:12px 20px;border-top:1px solid rgba(0,0,0,.06);background:linear-gradient(180deg,#fafafa,#f7f7f7)}.data-grid-container .pagination-controls button{transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:6px}.data-grid-container .pagination-controls button:hover:not(:disabled){background-color:#0000000a;transform:scale(1.02)}.data-grid-container .pagination-controls button:active:not(:disabled){transform:scale(.98);background-color:#00000014}.data-grid-container .pagination-controls button:disabled{cursor:not-allowed;opacity:.3}.data-grid-container .pagination-controls button.active{background-color:#3b82f61a;color:#3b82f6;font-weight:500;box-shadow:0 0 0 1px #3b82f633}.data-grid-container .pagination-controls input[type=number]{transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(0,0,0,.06);border-radius:6px}.data-grid-container .pagination-controls input[type=number]:hover{border-color:#0000001a}.data-grid-container .pagination-controls input[type=number]:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .status-badge{font-weight:500;letter-spacing:-.01em;padding:4px 12px;border-radius:12px;font-size:12px;display:inline-flex;align-items:center;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .status-badge.active{background-color:#34d39926;color:#059669;border:1px solid rgba(52,211,153,.3)}.data-grid-container .status-badge.active:hover{background-color:#34d39933}.data-grid-container .status-badge.inactive{background-color:#f8717126;color:#dc2626;border:1px solid rgba(248,113,113,.3)}.data-grid-container .status-badge.inactive:hover{background-color:#f8717133}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper{transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:hover:not(:has(input:disabled)){background-color:#00000005}.data-grid-container .data-grid-search-input ::ng-deep input{font-size:13px;font-weight:400;color:#1f2937;transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep input::placeholder{color:#9ca3af;font-weight:400}.data-grid-container .data-grid-search-input ::ng-deep input:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep input:disabled{background-color:#00000005;color:#9ca3af;cursor:not-allowed}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-leading-icon cide-ele-icon{color:#9ca3af;transition:color .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep input:focus~.cide-input-leading-icon cide-ele-icon,.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:has(input:focus) .cide-input-leading-icon cide-ele-icon{color:#6b7280}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]{border:1px solid #e5e7eb;background-color:#fafafa;border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:hover:not(:has(input:disabled)){border-color:#d1d5db;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:has(input:focus){border-color:#3b82f64d;background-color:#fff}.data-grid-container .search-input{position:relative}.data-grid-container .search-input input{transition:all .15s ease-in-out}.data-grid-container .search-input input:focus{box-shadow:0 0 0 3px #3b82f61a}.data-grid-container .search-input .search-icon{pointer-events:none}.data-grid-container .column-menu-dropdown{animation:dropdownFadeIn .15s cubic-bezier(.4,0,.2,1);box-shadow:0 10px 25px #0000001a,0 4px 10px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(0,0,0,.05)}.data-grid-container .column-menu-dropdown button{font-size:13px;font-weight:400;letter-spacing:-.01em;text-align:left;transition:all .15s cubic-bezier(.4,0,.2,1)}.data-grid-container .column-menu-dropdown button:hover{background-color:#3b82f60d;color:#1f2937}.data-grid-container .column-menu-dropdown button:hover cide-ele-icon{color:#3b82f6}.data-grid-container .column-menu-dropdown button:active{background-color:#3b82f61a;transform:scale(.98)}.data-grid-container .column-menu-dropdown .tw-uppercase{letter-spacing:.05em}@keyframes dropdownFadeIn{0%{opacity:0;transform:translateY(-8px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.data-grid-container .empty-state{padding:4rem 2rem;text-align:center}.data-grid-container .empty-state svg{margin:0 auto 1.5rem;opacity:.3;transition:all .3s cubic-bezier(.4,0,.2,1)}.data-grid-container .empty-state svg:hover{opacity:.5;transform:scale(1.05)}.data-grid-container .empty-state h3{margin-bottom:.75rem;font-weight:600;color:#374151;font-size:16px;letter-spacing:-.01em}.data-grid-container .empty-state p{color:#6b7280;font-size:14px;line-height:1.5}.data-grid-container.tw-h-full tbody tr:only-child td{height:100%;vertical-align:middle}.data-grid-container.tw-h-full table.empty-table{height:100%}.data-grid-container.tw-h-full tbody tr:only-child{height:100%}@media (max-width: 640px){.data-grid-container .tw-px-6{padding-left:1rem;padding-right:1rem}.data-grid-container .pagination-controls{flex-direction:column;gap:1rem}.data-grid-container .pagination-controls .flex{justify-content:center}.data-grid-container .pagination-info{display:none}.data-grid-container .search-actions{flex-direction:column;gap:1rem}}@media (prefers-color-scheme: dark){.data-grid-container.dark-mode{background-color:#1f2937;color:#f9fafb}.data-grid-container.dark-mode table{background-color:#1f2937}.data-grid-container.dark-mode thead{background-color:#374151}.data-grid-container.dark-mode tbody tr{border-color:#374151}.data-grid-container.dark-mode tbody tr:hover{background-color:#374151}.data-grid-container.dark-mode .tw-text-gray-900{color:#f9fafb!important}.data-grid-container.dark-mode .tw-text-gray-600{color:#d1d5db!important}.data-grid-container.dark-mode .tw-text-gray-500{color:#9ca3af!important}}.data-grid-container.drag-drop-enabled tbody tr{transition:all .2s ease}.data-grid-container.drag-drop-enabled tbody tr:hover{background-color:#f3f4f6}.data-grid-container.drag-drop-enabled tbody tr.tw-opacity-50{background-color:#dbeafecc;border:2px dashed #3b82f6;border-radius:4px}.data-grid-container.drag-drop-enabled tbody tr.tw-bg-blue-50{background-color:#eff6ffe6}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]{cursor:move;position:relative}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:hover{background-color:#f8fafc;box-shadow:0 1px 3px #0000001a}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:active{cursor:grabbing}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]{position:relative}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]:before{content:\"\";position:absolute;top:-2px;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);z-index:10}.data-grid-container.tree-enabled tbody tr td:first-child{position:relative}.data-grid-container.tree-enabled tbody tr td:first-child button{transition:all .15s ease}.data-grid-container.tree-enabled tbody tr td:first-child button:hover{background-color:#f3f4f6;border-radius:2px}.data-grid-container.tree-enabled tbody tr td:first-child button svg{transition:transform .2s ease}.data-grid-container.tree-enabled tbody tr[style*=padding-left]{border-left:2px solid transparent}.data-grid-container.tree-enabled tbody tr[style*=padding-left]:hover{border-left-color:#e5e7eb}.tree-level-0{border-bottom:2px solid #e5e7eb!important;background-color:#fff}.tree-level-0:hover{background-color:#f5f5f5!important}.tree-level-0 td:first-child{font-weight:600}.tree-level-1{border-bottom:1px solid #d1d5db!important;background-color:#fafafa}.tree-level-1:hover{background-color:#f1f5f9!important}.tree-level-1 td:first-child{font-weight:500}.tree-level-2{border-bottom:1px solid #d1d5db!important;background-color:#f9fafb}.tree-level-2:hover{background-color:#f3f4f6!important}.tree-level-2 td:first-child{font-weight:400}.tree-level-deep{border-bottom:1px solid #e5e7eb!important;background-color:#fefefe}.tree-level-deep:hover{background-color:#f9fafb!important}.tree-level-deep td:first-child{font-weight:300}table td{box-sizing:border-box;word-wrap:break-word;overflow-wrap:break-word}table td.tw-overflow-hidden{overflow:hidden;text-overflow:ellipsis}table td:first-child{min-width:150px}table td:first-child>div{display:flex;align-items:center;min-width:0}table td:first-child>div .tw-flex{min-width:0}table td .tw-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}\n"] }]
|
|
10407
|
+
], template: " <!-- Data Grid Component -->\r\n <div class=\"data-grid-container tw-bg-white tw-shadow tw-rounded-lg tw-overflow-visible tw-flex tw-flex-col\" \r\n [ngClass]=\"[\r\n mergedConfig().tableClass || '',\r\n mergedConfig().fullHeight ? 'tw-h-full' : '',\r\n isDragDropEnabled() ? 'drag-drop-enabled' : '',\r\n isTreeEnabled() ? 'tree-enabled' : ''\r\n ]\">\r\n \r\n <!-- Header Section -->\r\n @if (mergedConfig().title || mergedConfig().subtitle) {\r\n <div class=\"tw-px-3 tw-py-2 tw-border-b tw-border-gray-200\">\r\n @if (mergedConfig().title) {\r\n <h3 class=\"tw-text-base tw-font-semibold tw-text-gray-900\">\r\n {{ mergedConfig().title }}\r\n </h3>\r\n }\r\n @if (mergedConfig().subtitle) {\r\n <p class=\"tw-text-sm tw-text-gray-600 tw-mt-0.5\">\r\n {{ mergedConfig().subtitle }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Search Section -->\r\n @if (searchConfig.enabled) {\r\n <div class=\"tw-px-3 tw-py-1.5 tw-border-b tw-border-gray-200\">\r\n <div class=\"tw-flex tw-items-center tw-justify-between\">\r\n <!-- Left Side: Search Input and Action Icons -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1.5\">\r\n <!-- Search Input - Apple Style -->\r\n <div class=\"tw-max-w-md data-grid-search-input\">\r\n <cide-ele-input [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\" id=\"search-input\" type=\"text\"\r\n [ngModel]=\"searchQuery()\"\r\n (ngModelChange)=\"updateSearchQuery($event)\"\r\n [placeholder]=\"searchConfig.placeholder\"\r\n [disabled]=\"loading() || isRefreshing()\"\r\n leadingIcon=\"search\"\r\n fill=\"outline\"\r\n size=\"sm\">\r\n </cide-ele-input>\r\n </div>\r\n \r\n <!-- Action Icons (Filter, Sort, Download) - Apple Style Compact -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1 data-grid-action-buttons\">\r\n <!-- Filter Button -->\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Filter\"\r\n (click)=\"onActionClick(null, { key: 'filter', label: 'Filter', icon: 'filter_list', variant: 'ghost', onClick: 'filter' })\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">filter_list</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Download/Export Button with Dropdown -->\r\n <div class=\"tw-relative\">\r\n <button type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200\"\r\n title=\"Export\"\r\n (click)=\"toggleExportMenu($event)\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">file_download</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Export Dropdown Menu - Improved Design -->\r\n @if (showExportMenu()) {\r\n <div class=\"tw-absolute tw-right-0 tw-mt-2 tw-w-56 tw-bg-white tw-rounded-lg tw-shadow-xl tw-border tw-border-gray-200 tw-z-50 tw-overflow-hidden\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"tw-py-1.5\">\r\n <div class=\"tw-px-3 tw-py-2 tw-bg-gray-50 tw-border-b tw-border-gray-200\">\r\n <span class=\"tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase tw-tracking-wider\">Export Options</span>\r\n </div>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-green-50 hover:tw-text-green-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\r\n (click)=\"exportData('csv')\">\r\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-green-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-green-600\">description</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-flex-1\">\r\n <div class=\"tw-font-medium\">CSV</div>\r\n <div class=\"tw-text-xs tw-text-gray-500\">Comma separated values</div>\r\n </div>\r\n </button>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-blue-50 hover:tw-text-blue-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\r\n (click)=\"exportData('excel')\">\r\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-blue-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-blue-600\">table_chart</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-flex-1\">\r\n <div class=\"tw-font-medium\">Excel</div>\r\n <div class=\"tw-text-xs tw-text-gray-500\">Microsoft Excel format</div>\r\n </div>\r\n </button>\r\n <button type=\"button\"\r\n class=\"tw-w-full tw-text-left tw-px-4 tw-py-3 tw-text-sm tw-text-gray-700 hover:tw-bg-red-50 hover:tw-text-red-700 tw-flex tw-items-center tw-transition-colors tw-duration-150\"\r\n (click)=\"exportData('pdf')\">\r\n <div class=\"tw-w-8 tw-h-8 tw-rounded tw-bg-red-100 tw-flex tw-items-center tw-justify-center tw-mr-3\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-red-600\">picture_as_pdf</cide-ele-icon>\r\n </div>\r\n <div class=\"tw-flex-1\">\r\n <div class=\"tw-font-medium\">PDF</div>\r\n <div class=\"tw-text-xs tw-text-gray-500\">Portable document format</div>\r\n </div>\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <!-- Right Side: Drag Order Actions -->\r\n @if (isDragDropEnabled() && (isDragging() || hasOrderChanged())) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <button cideEleButton \r\n variant=\"outline\" \r\n size=\"sm\" \r\n type=\"button\"\r\n (click)=\"onActionClick(null, { key: 'reset-order', label: 'Reset Order', icon: 'undo', variant: 'outline', onClick: 'resetOrder' })\"\r\n class=\"tw-text-blue-700 tw-border-blue-300 hover:tw-bg-blue-100\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">undo</cide-ele-icon>\r\n Reset Order\r\n </button>\r\n <button cideEleButton \r\n variant=\"primary\" \r\n size=\"sm\" \r\n type=\"button\"\r\n (click)=\"onActionClick(null, { key: 'save-order', label: 'Save Order', icon: 'save', variant: 'primary', onClick: 'saveOrder' })\"\r\n class=\"tw-bg-blue-600 hover:tw-bg-blue-700 tw-text-white\">\r\n <cide-ele-icon size=\"xs\" class=\"tw-w-4 tw-h-4 tw-mr-1\">save</cide-ele-icon>\r\n Save Order\r\n </button>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Table Section -->\r\n <div class=\"tw-overflow-x-auto tw-relative\"\r\n [ngClass]=\"{\r\n 'tw-flex-1 tw-min-h-0': mergedConfig().fullHeight,\r\n 'tw-overflow-y-auto': scrollConfig?.enabled,\r\n 'tw-max-h-full': scrollConfig?.enabled\r\n }\"\r\n [style.maxHeight]=\"scrollConfig?.enabled ? scrollConfig?.maxHeight : null\"\r\n [style.minHeight]=\"scrollConfig?.enabled ? scrollConfig?.minHeight : null\">\r\n <table class=\"tw-min-w-full tw-divide-y tw-divide-gray-200 tw-h-full tw-table-fixed\"\r\n [class.empty-table]=\"displayedData.length === 0\"\r\n [ngClass]=\"{\r\n 'tw-table-striped': mergedConfig().striped,\r\n 'tw-border': mergedConfig().bordered,\r\n 'tw-table-sm': mergedConfig().compact\r\n }\"\r\n style=\"table-layout: fixed;\">\r\n \r\n <!-- Table Header -->\r\n <thead class=\"tw-bg-gray-50\" \r\n [ngClass]=\"[\r\n mergedConfig().headerClass || '',\r\n scrollConfig?.enabled && scrollConfig?.stickyHeader ? 'tw-sticky tw-top-0 tw-z-10' : ''\r\n ]\">\r\n <tr>\r\n @for (column of visibleColumns(); track column.key) {\r\n <th\r\n class=\"tw-px-3 tw-py-2 tw-text-left tw-text-xs tw-font-medium tw-text-gray-500 tw-uppercase tw-tracking-wider tw-relative\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width),\r\n column.align === 'center' ? 'tw-text-center' : '',\r\n column.align === 'right' ? 'tw-text-right' : ''\r\n ]\"\r\n [title]=\"column.header\">\r\n <div class=\"tw-flex tw-items-center tw-justify-between tw-gap-1\">\r\n <span class=\"tw-truncate tw-flex-1\">{{ column.header }}</span>\r\n \r\n <!-- Active Filter Indicator -->\r\n @if (isColumnFiltered(column.key)) {\r\n <div class=\"tw-inline-flex tw-items-center tw-px-1.5 tw-py-0.5 tw-rounded tw-bg-blue-100 tw-text-blue-700 tw-text-xs tw-font-medium tw-mr-1\">\r\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-mr-0.5\">filter_alt</cide-ele-icon>\r\n {{ getActiveFilterCount(column.key) }}\r\n </div>\r\n }\r\n \r\n <!-- Column Menu Trigger (Three Dots Icon) -->\r\n @if (mergedConfig().columnMenu?.enabled) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-rounded tw-text-gray-600 hover:tw-text-gray-800 hover:tw-bg-gray-100 tw-transition-all column-menu-trigger\"\r\n [class.tw-text-blue-600]=\"isColumnMenuOpen(column.key) || isColumnFiltered(column.key)\"\r\n [class.tw-bg-blue-50]=\"isColumnFiltered(column.key)\"\r\n (click)=\"toggleColumnMenu(column.key, $event)\"\r\n title=\"Column options\">\r\n <cide-ele-icon class=\"tw-w-5 tw-h-4\">more_vert</cide-ele-icon>\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Column Menu Dropdown -->\r\n @if (isColumnMenuOpen(column.key)) {\r\n <div class=\"column-menu-dropdown tw-absolute tw-z-50 tw-mt-2 tw-w-56 tw-rounded-lg tw-shadow-lg tw-bg-white tw-ring-1 tw-ring-black tw-ring-opacity-5\">\r\n <div class=\"tw-py-1\">\r\n <!-- Sort Options -->\r\n @if (mergedConfig().columnMenu?.showSort && column.sortable !== false) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnSort(column, 'asc')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\r\n Ascending\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnSort(column, 'desc')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\r\n Descending\r\n </button>\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n }\r\n\r\n <!-- Filter Option -->\r\n @if (mergedConfig().columnMenu?.showFilter && column.filterable !== false) {\r\n <!-- Check if there's a custom filter renderer template -->\r\n @if (column.filterRenderer && templateRenderers[column.filterRenderer]) {\r\n <div class=\"tw-px-4 tw-py-2\">\r\n <ng-container [ngTemplateOutlet]=\"$any(templateRenderers[column.filterRenderer])\"\r\n [ngTemplateOutletContext]=\"{ $implicit: column, column: column, onFilter: onColumnFilter.bind(this) }\">\r\n </ng-container>\r\n </div>\r\n } @else {\r\n <!-- Excel-style Filter Button -->\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n [class.tw-bg-blue-50]=\"isFilterPanelOpen(column.key)\"\r\n (click)=\"toggleFilterPanel(column.key, $event)\">\r\n <div class=\"tw-flex tw-items-center\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">filter_list</cide-ele-icon>\r\n Filter\r\n </div>\r\n <cide-ele-icon class=\"tw-w-3 tw-h-3 tw-text-gray-400\">\r\n {{ isFilterPanelOpen(column.key) ? 'expand_less' : 'expand_more' }}\r\n </cide-ele-icon>\r\n </button>\r\n \r\n <!-- Excel-style Filter Panel -->\r\n @if (isFilterPanelOpen(column.key)) {\r\n <div class=\"tw-px-2 tw-py-2 tw-bg-gray-50\" (click)=\"$event.stopPropagation()\">\r\n <!-- Search box -->\r\n <div class=\"tw-px-2 tw-mb-2\">\r\n <input\r\n type=\"text\"\r\n class=\"tw-w-full tw-px-2 tw-py-1 tw-text-xs tw-border tw-border-gray-300 tw-rounded focus:tw-outline-none focus:tw-ring-1 focus:tw-ring-blue-500\"\r\n placeholder=\"Search...\"\r\n [(ngModel)]=\"filterSearchTerm\"\r\n (click)=\"$event.stopPropagation()\">\r\n </div>\r\n \r\n <!-- Select All / Deselect All -->\r\n <div class=\"tw-px-2 tw-mb-1 tw-flex tw-gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\r\n (click)=\"selectAllFilterValues(column); $event.stopPropagation()\">\r\n Select All\r\n </button>\r\n <span class=\"tw-text-xs tw-text-gray-400\">|</span>\r\n <button\r\n type=\"button\"\r\n class=\"tw-text-xs tw-text-blue-600 hover:tw-text-blue-700\"\r\n (click)=\"deselectAllFilterValues(column); $event.stopPropagation()\">\r\n Clear\r\n </button>\r\n </div>\r\n \r\n <!-- Filter values list -->\r\n <div class=\"tw-max-h-48 tw-overflow-y-auto\">\r\n @for (item of getUniqueColumnValues(column); track item.value) {\r\n <label class=\"tw-flex tw-items-center tw-px-2 tw-py-1 tw-text-xs hover:tw-bg-white tw-cursor-pointer tw-rounded\">\r\n <input\r\n type=\"checkbox\"\r\n class=\"tw-mr-2 tw-rounded tw-border-gray-300 tw-text-blue-600 focus:tw-ring-blue-500\"\r\n [checked]=\"item.checked\"\r\n (change)=\"toggleFilterValue(column, item.value, $any($event.target).checked)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <span class=\"tw-flex-1 tw-truncate\">{{ item.label }}</span>\r\n <span class=\"tw-text-gray-400 tw-ml-1\">({{ item.count }})</span>\r\n </label>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n }\r\n\r\n <!-- Autosize Option -->\r\n @if (mergedConfig().columnMenu?.showAutosize) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAutosize(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">fit_screen</cide-ele-icon>\r\n Autosize\r\n </button>\r\n }\r\n\r\n <!-- Group By Column Option -->\r\n @if (mergedConfig().columnMenu?.showGroupBy && column.groupable !== false) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnGroupBy(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">group_work</cide-ele-icon>\r\n Group By Column\r\n </button>\r\n }\r\n\r\n <!-- Manage Columns Option -->\r\n @if (mergedConfig().columnMenu?.showManageColumns) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onManageColumns()\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">view_column</cide-ele-icon>\r\n Manage Columns\r\n </button>\r\n }\r\n\r\n <!-- Reset Columns Option -->\r\n @if (mergedConfig().columnMenu?.showResetColumns) {\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnReset()\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">refresh</cide-ele-icon>\r\n Reset Columns\r\n </button>\r\n }\r\n\r\n <!-- Hide Column Option -->\r\n @if (mergedConfig().columnMenu?.showHideColumn && column.hideable !== false) {\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnHide(column)\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">visibility_off</cide-ele-icon>\r\n Hide Column\r\n </button>\r\n }\r\n\r\n <!-- Aggregation Select Option -->\r\n @if (mergedConfig().columnMenu?.showAggregation && column.aggregatable !== false) {\r\n <div class=\"tw-border-t tw-border-gray-100 tw-my-1\"></div>\r\n <div class=\"tw-px-4 tw-py-2 tw-text-xs tw-font-semibold tw-text-gray-500 tw-uppercase\">Aggregation</div>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'sum')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">functions</cide-ele-icon>\r\n Sum\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'avg')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">analytics</cide-ele-icon>\r\n Average\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'count')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">tag</cide-ele-icon>\r\n Count\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'min')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_downward</cide-ele-icon>\r\n Min\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"tw-w-full tw-flex tw-items-center tw-px-4 tw-py-2 tw-text-sm tw-text-gray-700 hover:tw-bg-gray-50 tw-transition-colors\"\r\n (click)=\"onColumnAggregation(column, 'max')\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-mr-3 tw-text-gray-400\">arrow_upward</cide-ele-icon>\r\n Max\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </th>\r\n }\r\n </tr>\r\n </thead>\r\n\r\n <!-- Table Body -->\r\n <tbody class=\"tw-bg-white tw-divide-y tw-divide-gray-200\">\r\n @if (loading() || isRefreshing() || pageChangeLoading()) {\r\n <!-- Skeleton Loading Rows -->\r\n @for (skeletonItem of getSkeletonArray(); track $index) {\r\n <tr class=\"tw-animate-pulse tw-border-b tw-border-gray-200\">\r\n @for (column of columns; track column.key) {\r\n <td class=\"tw-px-3 tw-py-2 tw-whitespace-nowrap\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width)\r\n ]\">\r\n <div class=\"tw-h-2 tw-bg-gray-200 tw-rounded tw-w-3/4\"></div>\r\n </td>\r\n }\r\n </tr>\r\n }\r\n } @else {\r\n @for (item of displayedData; track trackByFn($index, item)) {\r\n <tr class=\"tw-group hover:tw-bg-gray-50 tw-border-b-2 tw-border-gray-200\"\r\n [ngClass]=\"[\r\n mergedConfig().rowClass || '',\r\n isRefreshing() ? 'tw-opacity-60 tw-pointer-events-none' : '',\r\n isDragDropEnabled() ? 'tw-cursor-move tw-border-2 tw-border-transparent' : '',\r\n !isDragDropEnabled() ? 'tw-transition-colors tw-duration-150' : '',\r\n isTreeEnabled() ? getTreeLevelClass(item) : ''\r\n ]\"\r\n [style.border-color]=\"isDragOverRow === $index ? '#3b82f6' : 'transparent'\"\r\n [style.background-color]=\"isDragOverRow === $index ? '#eff6ff' : ''\"\r\n (click)=\"onRowClick(item)\"\r\n (keydown.enter)=\"onRowClick(item)\"\r\n (keydown.space)=\"onRowClick(item)\"\r\n [class.tw-cursor-pointer]=\"mergedConfig().onRowClick && !isDragDropEnabled()\"\r\n [tabindex]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 0 : -1\"\r\n [attr.role]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'button' : null\"\r\n [attr.aria-label]=\"mergedConfig().onRowClick && !isDragDropEnabled() ? 'Select row' : null\"\r\n [draggable]=\"isDragDropEnabled()\"\r\n (dragstart)=\"onDragStart($event, item, $index)\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragleave)=\"onDragLeave($event)\"\r\n (drop)=\"onDrop($event, item, $index)\"\r\n (dragend)=\"onDragEnd($event)\">\r\n \r\n @for (column of columns; track column.key) {\r\n <td class=\"tw-pr-3 tw-py-1 tw-relative\"\r\n [ngClass]=\"[\r\n getColumnWidthClass(column.width),\r\n getColumnMaxWidthClass(column.width),\r\n mergedConfig().cellClass || '',\r\n column.align === 'center' ? 'tw-text-center' : '',\r\n column.align === 'right' ? 'tw-text-right' : '',\r\n column.truncate !== false ? 'tw-whitespace-nowrap' : 'tw-whitespace-normal'\r\n ]\"\r\n [style.paddingLeft]=\"isTreeEnabled() && $index === 0 ? getTreeIndentStyle(item) : '12px'\"\r\n [style.maxWidth]=\"getColumnMaxWidthClass(column.width) === 'tw-max-w-xs' ? '200px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-sm' ? '300px' : getColumnMaxWidthClass(column.width) === 'tw-max-w-md' ? '400px' : 'none'\"\r\n [style.minWidth]=\"isTreeEnabled() && $index === 0 ? '150px' : '100px'\">\r\n <!-- Tree Expand/Collapse Button (only for first column when tree is enabled) -->\r\n @if (isTreeEnabled() && $index === 0) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <!-- Tree Indentation -->\r\n <div class=\"tw-flex tw-items-center\">\r\n @if (hasChildren(item)) {\r\n <button \r\n variant=\"outline\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, { key: 'toggle-expand', label: 'Toggle', icon: '', variant: 'ghost', onClick: 'toggle-expand' }); $event.stopPropagation()\"\r\n class=\"tw-flex tw-items-center tw-justify-center tw-w-5 tw-h-5 tw-text-gray-500 hover:tw-text-gray-700 tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [title]=\"isItemExpanded(item) ? 'Collapse' : 'Expand'\">\r\n <cide-ele-icon \r\n class=\"tw-w-3 tw-h-3\"\r\n [class.tw-transition-transform]=\"!isDragDropEnabled()\"\r\n [class.tw-rotate-90]=\"isItemExpanded(item)\"\r\n size=\"xs\">\r\n chevron_right\r\n </cide-ele-icon>\r\n </button>\r\n } @else {\r\n <div class=\"tw-w-8 tw-h-5 tw-flex tw-items-center tw-justify-center\">\r\n <!-- <div class=\"tw-w-1 tw-h-1 tw-bg-gray-300 tw-rounded-full\"></div> -->\r\n </div>\r\n }\r\n </div>\r\n \r\n <!-- Cell Content -->\r\n <div class=\"tw-flex-1 tw-w-full\">\r\n @if (column.type === 'text') {\r\n <p class=\"tw-text-sm tw-text-gray-900\"\r\n [class.tw-truncate]=\"column.truncate\"\r\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </p>\r\n } @else if (column.type === 'number') {\r\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'date') {\r\n <span class=\"tw-text-sm tw-text-gray-600\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'boolean') {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\r\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\r\n </span>\r\n } @else if (column.type === 'status' && column.statusConfig) {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\r\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\r\n </span>\r\n } @else if (column.type === 'actions' && column.actions) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n @for (action of column.actions; track action.key) {\r\n <button\r\n cideEleButton\r\n [variant]=\"action.variant || 'ghost'\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\r\n [title]=\"action.tooltip || action.label\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [ngClass]=\"{\r\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\r\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\r\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\r\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\r\n }\">\r\n @if (action.icon) {\r\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\r\n </svg>\r\n }\r\n {{ action.label }}\r\n </button>\r\n }\r\n </div>\r\n } @else if (column.type === 'custom') {\r\n <!-- Template Renderer -->\r\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\r\n <ng-container \r\n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\r\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\r\n </ng-container>\r\n }\r\n <!-- Default rendering -->\r\n @else {\r\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\r\n }\r\n }\r\n </div>\r\n </div>\r\n } @else {\r\n <!-- Regular cell content (non-tree or non-first column) -->\r\n @if (column.type === 'text') {\r\n <p class=\"tw-text-sm tw-text-gray-900\"\r\n [class.tw-truncate]=\"column.truncate\"\r\n [title]=\"column.truncate ? (getNestedValue(item, column.valueGetter || column.key) || '').toString() : ''\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </p>\r\n } @else if (column.type === 'number') {\r\n <span class=\"tw-text-sm tw-text-gray-900 tw-font-mono\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'date') {\r\n <span class=\"tw-text-sm tw-text-gray-600\">\r\n {{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}\r\n </span>\r\n } @else if (column.type === 'boolean') {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getNestedValue(item, column.valueGetter || column.key) ? 'tw-bg-green-100 tw-text-green-800' : 'tw-bg-red-100 tw-text-red-800'\">\r\n {{ getNestedValue(item, column.valueGetter || column.key) ? 'Yes' : 'No' }}\r\n </span>\r\n } @else if (column.type === 'status' && column.statusConfig) {\r\n <span class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-0.5 tw-rounded-full tw-text-xs tw-font-medium\"\r\n [ngClass]=\"getStatusClass(getNestedValue(item, column.valueGetter || column.key), column.statusConfig)\">\r\n {{ getStatusText(getNestedValue(item, column.valueGetter || column.key), column.statusConfig) }}\r\n </span>\r\n } @else if (column.type === 'actions' && column.actions) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n @for (action of column.actions; track action.key) {\r\n <button\r\n cideEleButton\r\n [variant]=\"action.variant || 'ghost'\"\r\n size=\"xs\"\r\n type=\"button\"\r\n (click)=\"onActionClick(item, action); $event.stopPropagation()\"\r\n [title]=\"action.tooltip || action.label\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-px-2 tw-py-2 tw-text-xs tw-font-medium tw-rounded\"\r\n [class.tw-transition-colors]=\"!isDragDropEnabled()\"\r\n [ngClass]=\"{\r\n 'tw-text-gray-700 tw-bg-gray-100 hover:tw-bg-gray-200': action.variant === 'ghost',\r\n 'tw-text-white tw-bg-blue-600 hover:tw-bg-blue-700': action.variant === 'primary',\r\n 'tw-text-blue-700 tw-bg-blue-50 tw-border tw-border-blue-200 hover:tw-bg-blue-100': action.variant === 'outline',\r\n 'tw-text-white tw-bg-red-600 hover:tw-bg-red-700': action.variant === 'danger'\r\n }\">\r\n @if (action.icon) {\r\n <svg class=\"tw-w-3 tw-h-3 tw-mr-1\" fill=\"currentColor\" viewBox=\"0 0 20 20\">\r\n <path d=\"M10 12l-5-5h10l-5 5z\"/>\r\n </svg>\r\n }\r\n {{ action.label }}\r\n </button>\r\n }\r\n </div>\r\n } @else if (column.type === 'custom') {\r\n <!-- Template Renderer -->\r\n @if (column.renderer && isTemplateRenderer(column.renderer)) {\r\n <ng-container \r\n [ngTemplateOutlet]=\"getTemplateRenderer(column.renderer)!\"\r\n [ngTemplateOutletContext]=\"getTemplateContext(getNestedValue(item, column.valueGetter || column.key), item, column)\">\r\n </ng-container>\r\n }\r\n <!-- Default rendering -->\r\n @else {\r\n <div>{{ formatValue(getNestedValue(item, column.valueGetter || column.key), column) }}</div>\r\n }\r\n }\r\n }\r\n </td>\r\n }\r\n </tr>\r\n }\r\n \r\n <!-- Empty State -->\r\n @if (displayedData.length === 0) {\r\n <tr class=\"tw-h-full\">\r\n <td [attr.colspan]=\"columns.length\" class=\"tw-px-6 tw-py-22 tw-text-center tw-h-full tw-align-middle\">\r\n <div class=\"tw-text-gray-500 tw-flex tw-flex-col tw-items-center tw-justify-center tw-min-h-[300px]\">\r\n <svg class=\"tw-mx-auto tw-h-12 tw-w-12 tw-text-gray-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\r\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z\"></path>\r\n </svg>\r\n <h3 class=\"tw-mt-2 tw-text-sm tw-font-medium tw-text-gray-900\">No data found</h3>\r\n <p class=\"tw-mt-1 tw-text-sm tw-text-gray-500\">\r\n @if (searchQuery()) {\r\n No results match your search criteria.\r\n } @else {\r\n There are no items to display.\r\n }\r\n </p>\r\n </div>\r\n </td>\r\n </tr>\r\n }\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n <!-- Pagination Section -->\r\n @if (paginationConfig.enabled && totalItems() > 0) {\r\n <div class=\"tw-px-3 tw-py-0 tw-border-t tw-border-gray-200 tw-bg-white tw-relative tw-z-20\"\r\n [class.tw-opacity-60]=\"isRefreshing()\">\r\n \r\n <!-- Results Info and Page Size -->\r\n <div class=\"tw-flex tw-flex-col sm:tw-flex-row tw-justify-between tw-items-start sm:tw-items-center tw-space-y-1 sm:tw-space-y-0\">\r\n \r\n <!-- Results Info -->\r\n @if (paginationConfig.showPageInfo) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-3\">\r\n <p class=\"tw-text-sm tw-text-gray-700\">\r\n Showing {{ getItemRangeText() }} results\r\n </p>\r\n \r\n <!-- Page Size Selector -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n <cide-ele-icon class=\"tw-w-4 tw-h-4 tw-text-gray-500\">view_list</cide-ele-icon>\r\n <span class=\"tw-text-sm tw-text-gray-700\">Per page:</span>\r\n <div class=\"tw-w-16 tw-relative\">\r\n <cide-ele-select\r\n [labelHide]=\"true\"\r\n [ngModel]=\"pageSize()\"\r\n (ngModelChange)=\"updatePageSize($event)\"\r\n [options]=\"getPageSizeOptions()\"\r\n [disabled]=\"isRefreshing()\"\r\n fill=\"outline\"\r\n size=\"xs\"\r\n class=\"tw-z-30\">\r\n </cide-ele-select>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Pagination Controls -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-2\">\r\n \r\n <!-- Previous/Next and Page Numbers -->\r\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\r\n \r\n <!-- First Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(1)\"\r\n [disabled]=\"currentPage() === 1 || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"First page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">first_page</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Previous Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"previousPage()\"\r\n [disabled]=\"!hasPreviousPage() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Previous page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_left</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Page Numbers -->\r\n @for (page of getEnhancedPageNumbers(); track page) {\r\n @if (page === '...') {\r\n <span class=\"tw-px-2 tw-py-2 tw-text-sm tw-text-gray-500\">...</span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(page)\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n [ngClass]=\"{\r\n 'tw-bg-blue-600 tw-text-white hover:tw-bg-blue-700': currentPage() === page,\r\n 'tw-bg-white tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-50': currentPage() !== page\r\n }\"\r\n [title]=\"'Page ' + page\">\r\n {{ page }}\r\n </button>\r\n }\r\n }\r\n \r\n <!-- Next Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"nextPage()\"\r\n [disabled]=\"!hasNextPage() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Next page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">chevron_right</cide-ele-icon>\r\n </button>\r\n \r\n <!-- Last Page -->\r\n <button\r\n type=\"button\"\r\n (click)=\"onPageChange(totalPages())\"\r\n [disabled]=\"currentPage() === totalPages() || isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Last page\">\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">last_page</cide-ele-icon>\r\n </button>\r\n \r\n </div>\r\n\r\n <!-- Quick Jump and Refresh -->\r\n @if (paginationConfig.showQuickJump || paginationConfig.showRefresh) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-1 tw-border-l tw-border-gray-200 tw-pl-2\">\r\n \r\n <!-- Quick Jump -->\r\n @if (paginationConfig.showQuickJump) {\r\n <div class=\"tw-flex tw-items-center tw-space-x-1\">\r\n <span class=\"tw-text-sm tw-text-gray-700\">Go to:</span>\r\n <div class=\"tw-w-16\">\r\n <cide-ele-input id=\"jump-to-page-input\" type=\"number\" [labelHide]=\"true\" [hideHelperAndErrorText]=\"true\"\r\n [(ngModel)]=\"jumpToPage\" [min]=\"1\" [max]=\"totalPages()\"\r\n [disabled]=\"isRefreshing()\"\r\n size=\"xs\"\r\n (keydown.enter)=\"onJumpToPage()\">\r\n </cide-ele-input>\r\n </div>\r\n <button\r\n type=\"button\"\r\n (click)=\"onJumpToPage()\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-6 tw-h-6 tw-p-0 tw-rounded tw-text-xs tw-font-medium tw-bg-gray-100 tw-text-gray-700 tw-border tw-border-gray-300 hover:tw-bg-gray-200 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Go to page\">\r\n Go\r\n </button>\r\n </div>\r\n }\r\n \r\n <!-- Refresh Button -->\r\n @if (paginationConfig.showRefresh) {\r\n <button\r\n type=\"button\"\r\n (click)=\"onRefresh()\"\r\n [disabled]=\"isRefreshing()\"\r\n class=\"tw-inline-flex tw-items-center tw-justify-center tw-w-7 tw-h-7 tw-p-0 tw-rounded-md tw-text-gray-500 hover:tw-text-gray-700 hover:tw-bg-gray-100 tw-transition-all tw-duration-200 disabled:tw-opacity-50 disabled:tw-cursor-not-allowed\"\r\n title=\"Refresh\">\r\n @if (isRefreshing()) {\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4 tw-animate-spin\">refresh</cide-ele-icon>\r\n } @else {\r\n <cide-ele-icon class=\"tw-w-[22px] tw-h-4\">refresh</cide-ele-icon>\r\n }\r\n </button>\r\n }\r\n </div>\r\n }\r\n \r\n </div>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [".data-grid-container{width:100%;display:flex;flex-direction:column;min-height:400px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:14px;color:#1f2937;background-color:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px #0000000a,0 1px 2px #00000005;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.data-grid-container.tw-h-full{height:100%;min-height:100%}.data-grid-container .tw-overflow-x-auto{scrollbar-width:thin;scrollbar-color:#d1d5db #f9fafb}.data-grid-container .tw-overflow-x-auto.tw-flex-1{flex:1;min-height:0}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar{height:6px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-track{background:#f9fafb}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb{background:#d1d5db;border-radius:3px}.data-grid-container .tw-overflow-x-auto::-webkit-scrollbar-thumb:hover{background:#9ca3af}.data-grid-container table{min-height:300px;border-collapse:separate;border-spacing:0;width:100%;background-color:#fff}.data-grid-container thead{background:linear-gradient(180deg,#fafafa,#f7f7f7);border-bottom:1px solid rgba(0,0,0,.06);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.data-grid-container thead th{background:transparent;color:#6b7280;font-weight:500;font-size:12px;text-transform:none;letter-spacing:-.01em;padding:4px 10px;border-bottom:1px solid rgba(0,0,0,.06);text-align:left;white-space:nowrap;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container thead th:first-child{padding-left:12px}.data-grid-container thead th:last-child{padding-right:12px}.data-grid-container thead th:hover{background-color:#00000005}.data-grid-container thead th .column-menu-trigger{opacity:1;transition:all .2s cubic-bezier(.4,0,.2,1);margin-left:4px;cursor:pointer;padding:2px;border-radius:4px}.data-grid-container thead th .column-menu-trigger:hover{background-color:#0000000f}.data-grid-container tbody{background-color:#fff}.data-grid-container tbody td{padding:6px 10px;border-bottom:1px solid rgba(0,0,0,.03);color:#1f2937;font-size:13px;vertical-align:middle;line-height:1.5;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody td:first-child{padding-left:12px}.data-grid-container tbody td:last-child{padding-right:12px}.data-grid-container tbody tr{background-color:#fff;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container tbody tr:hover{background-color:#00000005;transform:scale(1.001)}.data-grid-container tbody tr:hover td{border-bottom-color:#0000000a}.data-grid-container tbody tr:active{background-color:#00000008;transform:scale(.999)}.data-grid-container.tw-h-full table,.data-grid-container.tw-h-full tbody{height:100%}.data-grid-container.tw-table-striped tbody tr:nth-child(2n){background-color:#00000004}.data-grid-container.tw-table-striped tbody tr:nth-child(2n):hover{background-color:#00000008;transform:scale(1.001)}.data-grid-container.tw-table-sm thead th{padding:8px 10px;font-size:11px}.data-grid-container.tw-table-sm thead th:first-child{padding-left:16px}.data-grid-container.tw-table-sm thead th:last-child{padding-right:16px}.data-grid-container.tw-table-sm tbody td{padding:8px 10px;font-size:12px;line-height:1.4}.data-grid-container.tw-table-sm tbody td:first-child{padding-left:16px}.data-grid-container.tw-table-sm tbody td:last-child{padding-right:16px}.data-grid-container tbody tr.tw-cursor-pointer:hover{background-color:#e5f3ff}.data-grid-container.loading-overlay{position:relative}.data-grid-container.loading-overlay:after{content:\"\";position:absolute;inset:0;background:#ffffffd9;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);z-index:10;animation:fadeIn .2s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.data-grid-container .tw-animate-pulse div{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.data-grid-container .data-grid-action-buttons button{position:relative;border:1px solid rgba(0,0,0,.08);background:transparent;outline:none;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:6px;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button cide-ele-icon{color:#6b7280;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled){background-color:#0000000a;border-color:#0000001f}.data-grid-container .data-grid-action-buttons button:hover:not(:disabled) cide-ele-icon{color:#374151;transform:scale(1.05)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled){background-color:#00000014;border-color:#00000026;transform:scale(.95)}.data-grid-container .data-grid-action-buttons button:active:not(:disabled) cide-ele-icon{transform:scale(.98)}.data-grid-container .data-grid-action-buttons button:focus-visible{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f61f}.data-grid-container .data-grid-action-buttons button:disabled{cursor:not-allowed;opacity:.3;background:transparent!important;border-color:#0000000d}.data-grid-container .action-buttons{display:flex;gap:.25rem}.data-grid-container .action-buttons button{transition:all .15s ease-in-out}.data-grid-container .action-buttons button:hover:not(:disabled){transform:translateY(-1px);box-shadow:0 2px 4px #0000001a}.data-grid-container .action-buttons button:disabled{cursor:not-allowed;opacity:.5}.data-grid-container .pagination-controls{padding:12px 20px;border-top:1px solid rgba(0,0,0,.06);background:linear-gradient(180deg,#fafafa,#f7f7f7)}.data-grid-container .pagination-controls button{transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:6px}.data-grid-container .pagination-controls button:hover:not(:disabled){background-color:#0000000a;transform:scale(1.02)}.data-grid-container .pagination-controls button:active:not(:disabled){transform:scale(.98);background-color:#00000014}.data-grid-container .pagination-controls button:disabled{cursor:not-allowed;opacity:.3}.data-grid-container .pagination-controls button.active{background-color:#3b82f61a;color:#3b82f6;font-weight:500;box-shadow:0 0 0 1px #3b82f633}.data-grid-container .pagination-controls input[type=number]{transition:all .2s cubic-bezier(.4,0,.2,1);border:1px solid rgba(0,0,0,.06);border-radius:6px}.data-grid-container .pagination-controls input[type=number]:hover{border-color:#0000001a}.data-grid-container .pagination-controls input[type=number]:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .status-badge{font-weight:500;letter-spacing:-.01em;padding:4px 12px;border-radius:12px;font-size:12px;display:inline-flex;align-items:center;transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .status-badge.active{background-color:#34d39926;color:#059669;border:1px solid rgba(52,211,153,.3)}.data-grid-container .status-badge.active:hover{background-color:#34d39933}.data-grid-container .status-badge.inactive{background-color:#f8717126;color:#dc2626;border:1px solid rgba(248,113,113,.3)}.data-grid-container .status-badge.inactive:hover{background-color:#f8717133}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper{transition:all .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:hover:not(:has(input:disabled)){background-color:#00000005}.data-grid-container .data-grid-search-input ::ng-deep input{font-size:13px;font-weight:400;color:#1f2937;transition:all .2s cubic-bezier(.4,0,.2,1);border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep input::placeholder{color:#9ca3af;font-weight:400}.data-grid-container .data-grid-search-input ::ng-deep input:focus{outline:none;border-color:#3b82f64d;box-shadow:0 0 0 3px #3b82f614;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep input:disabled{background-color:#00000005;color:#9ca3af;cursor:not-allowed}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-leading-icon cide-ele-icon{color:#9ca3af;transition:color .2s cubic-bezier(.4,0,.2,1)}.data-grid-container .data-grid-search-input ::ng-deep input:focus~.cide-input-leading-icon cide-ele-icon,.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper:has(input:focus) .cide-input-leading-icon cide-ele-icon{color:#6b7280}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]{border:1px solid #e5e7eb;background-color:#fafafa;border-radius:8px}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:hover:not(:has(input:disabled)){border-color:#d1d5db;background-color:#fff}.data-grid-container .data-grid-search-input ::ng-deep .cide-input-wrapper[data-fill=outline]:has(input:focus){border-color:#3b82f64d;background-color:#fff}.data-grid-container .search-input{position:relative}.data-grid-container .search-input input{transition:all .15s ease-in-out}.data-grid-container .search-input input:focus{box-shadow:0 0 0 3px #3b82f61a}.data-grid-container .search-input .search-icon{pointer-events:none}.data-grid-container .column-menu-dropdown{animation:dropdownFadeIn .15s cubic-bezier(.4,0,.2,1);box-shadow:0 10px 25px #0000001a,0 4px 10px #0000000d;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(0,0,0,.05)}.data-grid-container .column-menu-dropdown button{font-size:13px;font-weight:400;letter-spacing:-.01em;text-align:left;transition:all .15s cubic-bezier(.4,0,.2,1)}.data-grid-container .column-menu-dropdown button:hover{background-color:#3b82f60d;color:#1f2937}.data-grid-container .column-menu-dropdown button:hover cide-ele-icon{color:#3b82f6}.data-grid-container .column-menu-dropdown button:active{background-color:#3b82f61a;transform:scale(.98)}.data-grid-container .column-menu-dropdown .tw-uppercase{letter-spacing:.05em}@keyframes dropdownFadeIn{0%{opacity:0;transform:translateY(-8px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}.data-grid-container .empty-state{padding:4rem 2rem;text-align:center}.data-grid-container .empty-state svg{margin:0 auto 1.5rem;opacity:.3;transition:all .3s cubic-bezier(.4,0,.2,1)}.data-grid-container .empty-state svg:hover{opacity:.5;transform:scale(1.05)}.data-grid-container .empty-state h3{margin-bottom:.75rem;font-weight:600;color:#374151;font-size:16px;letter-spacing:-.01em}.data-grid-container .empty-state p{color:#6b7280;font-size:14px;line-height:1.5}.data-grid-container.tw-h-full tbody tr:only-child td{height:100%;vertical-align:middle}.data-grid-container.tw-h-full table.empty-table{height:100%}.data-grid-container.tw-h-full tbody tr:only-child{height:100%}@media (max-width: 640px){.data-grid-container .tw-px-6{padding-left:1rem;padding-right:1rem}.data-grid-container .pagination-controls{flex-direction:column;gap:1rem}.data-grid-container .pagination-controls .flex{justify-content:center}.data-grid-container .pagination-info{display:none}.data-grid-container .search-actions{flex-direction:column;gap:1rem}}@media (prefers-color-scheme: dark){.data-grid-container.dark-mode{background-color:#1f2937;color:#f9fafb}.data-grid-container.dark-mode table{background-color:#1f2937}.data-grid-container.dark-mode thead{background-color:#374151}.data-grid-container.dark-mode tbody tr{border-color:#374151}.data-grid-container.dark-mode tbody tr:hover{background-color:#374151}.data-grid-container.dark-mode .tw-text-gray-900{color:#f9fafb!important}.data-grid-container.dark-mode .tw-text-gray-600{color:#d1d5db!important}.data-grid-container.dark-mode .tw-text-gray-500{color:#9ca3af!important}}.data-grid-container.drag-drop-enabled tbody tr{transition:all .2s ease}.data-grid-container.drag-drop-enabled tbody tr:hover{background-color:#f3f4f6}.data-grid-container.drag-drop-enabled tbody tr.tw-opacity-50{background-color:#dbeafecc;border:2px dashed #3b82f6;border-radius:4px}.data-grid-container.drag-drop-enabled tbody tr.tw-bg-blue-50{background-color:#eff6ffe6}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]{cursor:move;position:relative}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:hover{background-color:#f8fafc;box-shadow:0 1px 3px #0000001a}.data-grid-container.drag-drop-enabled tbody tr[draggable=true]:active{cursor:grabbing}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]{position:relative}.data-grid-container.drag-drop-enabled tbody tr[style*=border-top]:before{content:\"\";position:absolute;top:-2px;left:0;right:0;height:2px;background:linear-gradient(90deg,#3b82f6,#60a5fa);z-index:10}.data-grid-container.tree-enabled tbody tr td:first-child{position:relative}.data-grid-container.tree-enabled tbody tr td:first-child button{transition:all .15s ease}.data-grid-container.tree-enabled tbody tr td:first-child button:hover{background-color:#f3f4f6;border-radius:2px}.data-grid-container.tree-enabled tbody tr td:first-child button svg{transition:transform .2s ease}.data-grid-container.tree-enabled tbody tr[style*=padding-left]{border-left:2px solid transparent}.data-grid-container.tree-enabled tbody tr[style*=padding-left]:hover{border-left-color:#e5e7eb}.tree-level-0{border-bottom:2px solid #e5e7eb!important;background-color:#fff}.tree-level-0:hover{background-color:#f5f5f5!important}.tree-level-0 td:first-child{font-weight:600}.tree-level-1{border-bottom:1px solid #d1d5db!important;background-color:#fafafa}.tree-level-1:hover{background-color:#f1f5f9!important}.tree-level-1 td:first-child{font-weight:500}.tree-level-2{border-bottom:1px solid #d1d5db!important;background-color:#f9fafb}.tree-level-2:hover{background-color:#f3f4f6!important}.tree-level-2 td:first-child{font-weight:400}.tree-level-deep{border-bottom:1px solid #e5e7eb!important;background-color:#fefefe}.tree-level-deep:hover{background-color:#f9fafb!important}.tree-level-deep td:first-child{font-weight:300}table td{box-sizing:border-box;word-wrap:break-word;overflow-wrap:break-word}table td.tw-overflow-hidden{overflow:hidden;text-overflow:ellipsis}table td:first-child{min-width:150px}table td:first-child>div{display:flex;align-items:center;min-width:0}table td:first-child>div .tw-flex{min-width:0}table td .tw-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}\n"] }]
|
|
10419
10408
|
}], propDecorators: { config: [{
|
|
10420
10409
|
type: Input
|
|
10421
10410
|
}], templateRenderers: [{
|