UNPKG

ng-table-pg

Version:

A powerful and flexible responsive table component for Angular applications with drag-drop, filtering, pagination, and advanced responsive features

1,060 lines (1,005 loc) 153 kB
import * as i0 from '@angular/core'; import { Injectable, Component, Input, Pipe, NgModule, ViewEncapsulation, EventEmitter, ViewChild, Output, HostListener } from '@angular/core'; import * as i1 from '@angular/common'; import { CommonModule } from '@angular/common'; import * as i3 from '@angular/forms'; import { FormsModule } from '@angular/forms'; import * as i1$1 from '@ngx-translate/core'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; import * as i4 from '@angular/cdk/drag-drop'; import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop'; import { Subject } from 'rxjs'; import { saveAs } from 'file-saver'; import * as XLSX from 'xlsx'; import { HttpClient } from '@angular/common/http'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; class TableService { constructor() { } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: TableService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: TableService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: TableService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [] }); class TableRowComponent { text; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: TableRowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.5", type: TableRowComponent, isStandalone: true, selector: "table-row", inputs: { text: "text" }, ngImport: i0, template: ` <span class="whitespace-nowrap">{{ text }}</span> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: TableRowComponent, decorators: [{ type: Component, args: [{ selector: 'table-row', standalone: true, imports: [CommonModule], template: ` <span class="whitespace-nowrap">{{ text }}</span> `, }] }], propDecorators: { text: [{ type: Input }] } }); class Web3UtilsPipe { transform(value, type, decimals = 18) { if (value == null) { return 'NO DATA'; } try { switch (type) { case 'wallet': return this.formatWalletAddress(String(value)); case 'wei': return this.fromWei(String(value), decimals); case 'token': return this.formatTokenAmount(String(value), decimals); default: return value; } } catch (error) { return type === 'wei' ? '0.0000' : String(value); } } formatWalletAddress(address) { return address.length >= 10 ? `${address.substring(0, 6)}...${address.substring(address.length - 4)}` : address; } fromWei(wei, decimals) { const value = Number(wei) / Math.pow(10, decimals); return isNaN(value) ? '0.0000' : value.toFixed(4); } formatTokenAmount(amount, decimals) { const value = Number(amount); if (isNaN(value)) return '0.00'; if (value >= 1000000) { return (value / 1000000).toFixed(2) + 'M'; } else if (value >= 1000) { return (value / 1000).toFixed(2) + 'K'; } return value.toFixed(2); // Changed to always use 2 decimals for consistency } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: Web3UtilsPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.5", ngImport: i0, type: Web3UtilsPipe, isStandalone: true, name: "web3Utils" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: Web3UtilsPipe, decorators: [{ type: Pipe, args: [{ name: 'web3Utils', standalone: true }] }] }); class PipesModule { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: PipesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.5", ngImport: i0, type: PipesModule, imports: [CommonModule, Web3UtilsPipe], exports: [Web3UtilsPipe] }); static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: PipesModule, imports: [CommonModule] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: PipesModule, decorators: [{ type: NgModule, args: [{ imports: [CommonModule, Web3UtilsPipe], exports: [Web3UtilsPipe], }] }] }); class TableRowContentComponent { item; column; itemValue; itemTransformed; isArrayItemValue = false; ngOnChanges(changes) { if (changes['itemValue']) { this.isArrayItemValue = Array.isArray(this.itemValue); this.itemTransformed = this.column.transform ? this.column.transform(this.itemValue, this.item) : this.itemValue; } } getStatus(step) { const [current, total] = step.split('/').map(Number); if (current === total) return 'completed'; if (current === 1) return 'pending'; return 'in-progress'; } getStatusClass(status) { const normalizedStatus = typeof status === 'string' ? status.toLowerCase() : status || 'inactive'; switch (normalizedStatus) { case 'active': case 'activa': case 'completed': case true: return 'bg-green-100 text-green-800'; case 'inactive': case 'inactiva': case 'refused': case 'rejected': case 'reject': case false: return 'bg-red-100 text-red-800'; case 'pending_to_send': case 'approved': return 'bg-blue-100 text-blue-800'; case 'partial': case 'in progress': return 'bg-yellow-100 text-yellow-800'; case 'pending': return 'bg-gray-100 text-gray-800'; default: return ''; } } getStatusClasses(status) { return { 'px-2 py-1 rounded-full text-xs font-semibold text-nowrap': true, [this.getStatusClass(status)]: true, }; } formatJson(value) { try { const parsed = typeof value === 'string' ? JSON.parse(value) : value; const formatIndentation = (str) => { const lines = str.split('\n'); let indent = 0; return lines .map((line) => { // Reducir indentación para líneas que cierran bloques if (line.includes('}') || line.includes(']')) { indent -= 2; } const currentIndent = ' '.repeat(Math.max(0, indent)); const formattedLine = currentIndent + line.trim(); // Aumentar indentación para la siguiente línea si esta abre un bloque if (line.includes('{') || line.includes('[')) { indent += 2; } return formattedLine; }) .join('\n'); }; return formatIndentation(JSON.stringify(parsed, null, 2)) .trim() .replace(/": "/g, '": "') // Espaciado consistente después de los dos puntos .replace(/,$/gm, ','); // Asegurar que las comas queden en la misma línea } catch (e) { return String(value); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: TableRowContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.5", type: TableRowContentComponent, isStandalone: true, selector: "table-row-content", inputs: { item: "item", column: "column", itemValue: "itemValue" }, usesOnChanges: true, ngImport: i0, template: ` <ng-container *ngIf="isArrayItemValue; else singleItemTemplate"> <ng-container *ngFor="let item of itemTransformed"> <span class="mr-2 last:mr-0"> <ng-container *ngTemplateOutlet="contentTemplate; context: { $implicit: item }" ></ng-container> </span> </ng-container> </ng-container> <ng-template #singleItemTemplate> <ng-container *ngTemplateOutlet=" contentTemplate; context: { $implicit: itemTransformed } " ></ng-container> </ng-template> <ng-template #contentTemplate let-value> <ng-container [ngSwitch]="column.type || column.key"> <!-- Caso 'step' --> <ng-container *ngSwitchCase="'step'"> <span [ngClass]="{ 'px-2 py-1 rounded-full text-xs font-semibold': true, 'bg-green-100 text-green-800': getStatus(item.step) === 'completed', 'bg-yellow-100 text-yellow-800': getStatus(item.step) === 'in-progress', 'bg-blue-100 text-blue-800': getStatus(item.step) === 'pending' }" > <table-row [text]="value"></table-row> </span> </ng-container> <!-- Caso 'status' --> <ng-container *ngSwitchCase="'status'"> <span [ngClass]="getStatusClasses(item[column.key])"> <span *ngIf="item[column.key] === undefined">Inactive</span> <ng-container [ngSwitch]="item.status || item[column.key]"> <span *ngSwitchCase="true">Active</span> <span *ngSwitchCase="false">Inactive</span> <span *ngSwitchCase="'partial'">Partial Active</span> <span *ngSwitchCase="'reject'">Rejected</span> <span *ngSwitchCase="'pending_to_send'">Pending to send</span> <span *ngSwitchCase="'pending'">Pending</span> <span *ngSwitchCase="'approved'">Approved</span> <table-row *ngSwitchDefault [text]="value | titlecase" ></table-row> </ng-container> </span> </ng-container> <!-- Caso 'HTML' --> <ng-container *ngSwitchCase="'html'"> <span [innerHTML]="value"></span> </ng-container> <!-- Caso 'status:expired' --> <ng-container *ngSwitchCase="'status:expired'"> <span [ngClass]="{ 'px-2 py-1 rounded-full text-xs font-semibold text-nowrap': true, 'bg-green-100 text-green-800':  item[column.key] === true, 'bg-red-100 text-red-800':  item[column.key] === false, }" > <ng-container [ngSwitch]="item[column.key]"> <span *ngSwitchCase="true">Active</span> <span *ngSwitchCase="false">Expired</span> <table-row *ngSwitchDefault [text]="value | titlecase" ></table-row> </ng-container> </span> </ng-container> <!-- Caso 'state' --> <ng-container *ngSwitchCase="'state'"> <span [ngClass]="{ 'px-2 py-1 rounded-full text-xs font-semibold text-nowrap': true, 'bg-blue-100 text-blue-800': item.state === 'pre-approved', 'bg-green-100 text-green-800': item.state === 'approved', 'bg-yellow-100 text-yellow-800': item.state === 'pending', 'bg-red-100 text-red-800': item.state === 'rejected' }" > <table-row [text]="value | titlecase"></table-row> </span> </ng-container> <!-- Caso 'date' --> <ng-container *ngSwitchCase="'date'"> <ng-container *ngIf="value"> <table-row [text]="value | date : 'yyyy-MM-dd'"></table-row> </ng-container> </ng-container> <!-- Caso 'boolean' --> <ng-container *ngSwitchCase="'boolean'"> {{ value ? 'Sí' : 'No' }} </ng-container> <!-- Caso 'date-time' --> <ng-container *ngSwitchCase="'date-time'"> <ng-container *ngIf="value"> <table-row [text]="value | date : 'dd/MM/yyyy HH:mm'"></table-row> </ng-container> </ng-container> <!-- Caso 'timestamp' --> <ng-container *ngSwitchCase="'timestamp'"> <ng-container *ngIf="value"> <table-row [text]="value.toDate() | date : 'dd/MM/yyyy HH:mm'" ></table-row> </ng-container> </ng-container> <!-- Caso 'json' --> <ng-container *ngSwitchCase="'json'"> <ng-container *ngIf="value"> <pre class="bg-slate-50 rounded-lg p-2 text-sm font-mono text-slate-700 overflow-x-auto max-h-[150px] overflow-y-auto shadow-sm border border-slate-200" ><code>{{ formatJson(value) }}</code></pre> </ng-container> </ng-container> <!-- Caso 'currency' --> <ng-container *ngSwitchCase="'currency'"> <table-row [text]="value | currency : 'USD' : 'symbol' : '1.4-4'" ></table-row> </ng-container> <!-- Casos adicionales --> <ng-container *ngSwitchCase="'uppercase'"> <table-row [text]="value | uppercase"></table-row> </ng-container> <ng-container *ngSwitchCase="'titlecase'"> <table-row [text]="value | titlecase"></table-row> </ng-container> <ng-container *ngSwitchCase="'wallet'"> <table-row [text]="value | web3Utils : 'wallet'"></table-row> </ng-container> <ng-container *ngSwitchCase="'badget'"> <span class="px-2 py-1 text-nowrap rounded-full text-xs font-semibold bg-gray-100 text-gray-800" > <table-row [text]="value"></table-row> </span> </ng-container> <ng-container *ngSwitchCase="'badget:info'"> <span class="px-2 py-1 rounded-full text-nowrap text-xs font-semibold bg-blue-100 text-blue-800" > <table-row [text]="value"></table-row> </span> </ng-container> <ng-container *ngSwitchDefault> <table-row [text]="value"></table-row> </ng-container> </ng-container> </ng-template> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgSwitch, selector: "[ngSwitch]", inputs: ["ngSwitch"] }, { kind: "directive", type: i1.NgSwitchCase, selector: "[ngSwitchCase]", inputs: ["ngSwitchCase"] }, { kind: "directive", type: i1.NgSwitchDefault, selector: "[ngSwitchDefault]" }, { kind: "pipe", type: i1.UpperCasePipe, name: "uppercase" }, { kind: "pipe", type: i1.TitleCasePipe, name: "titlecase" }, { kind: "pipe", type: i1.CurrencyPipe, name: "currency" }, { kind: "pipe", type: i1.DatePipe, name: "date" }, { kind: "component", type: TableRowComponent, selector: "table-row", inputs: ["text"] }, { kind: "ngmodule", type: PipesModule }, { kind: "pipe", type: Web3UtilsPipe, name: "web3Utils" }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: TableRowContentComponent, decorators: [{ type: Component, args: [{ selector: 'table-row-content', standalone: true, imports: [CommonModule, TableRowComponent, PipesModule], template: ` <ng-container *ngIf="isArrayItemValue; else singleItemTemplate"> <ng-container *ngFor="let item of itemTransformed"> <span class="mr-2 last:mr-0"> <ng-container *ngTemplateOutlet="contentTemplate; context: { $implicit: item }" ></ng-container> </span> </ng-container> </ng-container> <ng-template #singleItemTemplate> <ng-container *ngTemplateOutlet=" contentTemplate; context: { $implicit: itemTransformed } " ></ng-container> </ng-template> <ng-template #contentTemplate let-value> <ng-container [ngSwitch]="column.type || column.key"> <!-- Caso 'step' --> <ng-container *ngSwitchCase="'step'"> <span [ngClass]="{ 'px-2 py-1 rounded-full text-xs font-semibold': true, 'bg-green-100 text-green-800': getStatus(item.step) === 'completed', 'bg-yellow-100 text-yellow-800': getStatus(item.step) === 'in-progress', 'bg-blue-100 text-blue-800': getStatus(item.step) === 'pending' }" > <table-row [text]="value"></table-row> </span> </ng-container> <!-- Caso 'status' --> <ng-container *ngSwitchCase="'status'"> <span [ngClass]="getStatusClasses(item[column.key])"> <span *ngIf="item[column.key] === undefined">Inactive</span> <ng-container [ngSwitch]="item.status || item[column.key]"> <span *ngSwitchCase="true">Active</span> <span *ngSwitchCase="false">Inactive</span> <span *ngSwitchCase="'partial'">Partial Active</span> <span *ngSwitchCase="'reject'">Rejected</span> <span *ngSwitchCase="'pending_to_send'">Pending to send</span> <span *ngSwitchCase="'pending'">Pending</span> <span *ngSwitchCase="'approved'">Approved</span> <table-row *ngSwitchDefault [text]="value | titlecase" ></table-row> </ng-container> </span> </ng-container> <!-- Caso 'HTML' --> <ng-container *ngSwitchCase="'html'"> <span [innerHTML]="value"></span> </ng-container> <!-- Caso 'status:expired' --> <ng-container *ngSwitchCase="'status:expired'"> <span [ngClass]="{ 'px-2 py-1 rounded-full text-xs font-semibold text-nowrap': true, 'bg-green-100 text-green-800':  item[column.key] === true, 'bg-red-100 text-red-800':  item[column.key] === false, }" > <ng-container [ngSwitch]="item[column.key]"> <span *ngSwitchCase="true">Active</span> <span *ngSwitchCase="false">Expired</span> <table-row *ngSwitchDefault [text]="value | titlecase" ></table-row> </ng-container> </span> </ng-container> <!-- Caso 'state' --> <ng-container *ngSwitchCase="'state'"> <span [ngClass]="{ 'px-2 py-1 rounded-full text-xs font-semibold text-nowrap': true, 'bg-blue-100 text-blue-800': item.state === 'pre-approved', 'bg-green-100 text-green-800': item.state === 'approved', 'bg-yellow-100 text-yellow-800': item.state === 'pending', 'bg-red-100 text-red-800': item.state === 'rejected' }" > <table-row [text]="value | titlecase"></table-row> </span> </ng-container> <!-- Caso 'date' --> <ng-container *ngSwitchCase="'date'"> <ng-container *ngIf="value"> <table-row [text]="value | date : 'yyyy-MM-dd'"></table-row> </ng-container> </ng-container> <!-- Caso 'boolean' --> <ng-container *ngSwitchCase="'boolean'"> {{ value ? 'Sí' : 'No' }} </ng-container> <!-- Caso 'date-time' --> <ng-container *ngSwitchCase="'date-time'"> <ng-container *ngIf="value"> <table-row [text]="value | date : 'dd/MM/yyyy HH:mm'"></table-row> </ng-container> </ng-container> <!-- Caso 'timestamp' --> <ng-container *ngSwitchCase="'timestamp'"> <ng-container *ngIf="value"> <table-row [text]="value.toDate() | date : 'dd/MM/yyyy HH:mm'" ></table-row> </ng-container> </ng-container> <!-- Caso 'json' --> <ng-container *ngSwitchCase="'json'"> <ng-container *ngIf="value"> <pre class="bg-slate-50 rounded-lg p-2 text-sm font-mono text-slate-700 overflow-x-auto max-h-[150px] overflow-y-auto shadow-sm border border-slate-200" ><code>{{ formatJson(value) }}</code></pre> </ng-container> </ng-container> <!-- Caso 'currency' --> <ng-container *ngSwitchCase="'currency'"> <table-row [text]="value | currency : 'USD' : 'symbol' : '1.4-4'" ></table-row> </ng-container> <!-- Casos adicionales --> <ng-container *ngSwitchCase="'uppercase'"> <table-row [text]="value | uppercase"></table-row> </ng-container> <ng-container *ngSwitchCase="'titlecase'"> <table-row [text]="value | titlecase"></table-row> </ng-container> <ng-container *ngSwitchCase="'wallet'"> <table-row [text]="value | web3Utils : 'wallet'"></table-row> </ng-container> <ng-container *ngSwitchCase="'badget'"> <span class="px-2 py-1 text-nowrap rounded-full text-xs font-semibold bg-gray-100 text-gray-800" > <table-row [text]="value"></table-row> </span> </ng-container> <ng-container *ngSwitchCase="'badget:info'"> <span class="px-2 py-1 rounded-full text-nowrap text-xs font-semibold bg-blue-100 text-blue-800" > <table-row [text]="value"></table-row> </span> </ng-container> <ng-container *ngSwitchDefault> <table-row [text]="value"></table-row> </ng-container> </ng-container> </ng-template> `, }] }], propDecorators: { item: [{ type: Input }], column: [{ type: Input }], itemValue: [{ type: Input }] } }); class SkeletonTableComponent { static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: SkeletonTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.5", type: SkeletonTableComponent, isStandalone: true, selector: "ng-table-pg-skeleton", ngImport: i0, template: ` <div class="space-y-6"> <!-- Search and buttons --> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4" > <div class="skeleton h-10 w-full sm:w-2/3"></div> <div class="flex flex-col sm:flex-row gap-2"> <div class="skeleton h-10 w-full sm:w-24"></div> <div class="skeleton h-10 w-full sm:w-36"></div> </div> </div> <!-- Table Container with overflow --> <div class="bg-white rounded-lg shadow overflow-hidden"> <div class="overflow-x-auto"> <table class="w-full min-w-full"> <!-- Desktop Headers (hidden on mobile) --> <thead class="bg-gray-50 hidden md:table-header-group"> <tr> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-8"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-16"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-12"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-20"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-24"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-20"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-16"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-12"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-8"></div> </th> </tr> </thead> <!-- Mobile Headers (visible only on mobile) --> <thead class="bg-gray-50 md:hidden"> <tr> <th class="px-3 py-3 text-left"> <div class="skeleton h-4 w-16"></div> </th> <th class="px-3 py-3 text-left"> <div class="skeleton h-4 w-20"></div> </th> <th class="px-3 py-3 text-left"> <div class="skeleton h-4 w-12"></div> </th> <th class="px-3 py-3 text-left"> <div class="skeleton h-4 w-8"></div> </th> </tr> </thead> <tbody class="divide-y divide-gray-200"> <!-- Desktop Rows (hidden on mobile) --> @for (row of [1,2,3,4,5,6,7,8,9,10]; track row) { <tr class="border-t border-gray-200 hidden md:table-row"> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-8"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-20"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-6 w-8 rounded-full"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-32"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-28"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-24"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-16"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-6 w-16 rounded-full"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-6 w-6"></div> </td> </tr> } <!-- Mobile Rows (visible only on mobile) --> @for (row of [1,2,3,4,5,6,7,8,9,10]; track row) { <tr class="border-t border-gray-200 md:hidden"> <td class="px-3 py-4"> <div class="skeleton h-4 w-16"></div> </td> <td class="px-3 py-4"> <div class="skeleton h-4 w-20"></div> </td> <td class="px-3 py-4"> <div class="skeleton h-6 w-16 rounded-full"></div> </td> <td class="px-3 py-4"> <div class="skeleton h-6 w-6"></div> </td> </tr> } </tbody> </table> </div> </div> <!-- Pagination --> <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4" > <div class="flex items-center gap-2"> <div class="skeleton h-4 w-12"></div> <div class="skeleton h-8 w-12"></div> <div class="skeleton h-4 w-16"></div> </div> <div class="flex items-center justify-center sm:justify-end gap-2"> <div class="skeleton h-8 w-8"></div> <div class="skeleton h-8 w-8"></div> <div class="skeleton h-8 w-8"></div> <div class="skeleton h-4 w-4"></div> <div class="skeleton h-8 w-8"></div> <div class="skeleton h-8 w-8"></div> </div> </div> </div> `, isInline: true, styles: ["ng-table-pg-skeleton .skeleton{background-color:#e5e7eb!important;animation:ng-skeleton-pulse 2s cubic-bezier(.4,0,.6,1) infinite!important;border-radius:.25rem!important}@keyframes ng-skeleton-pulse{0%,to{opacity:1}50%{opacity:.5}}ng-table-pg-skeleton .flex{display:flex!important}ng-table-pg-skeleton .flex-col{flex-direction:column!important}ng-table-pg-skeleton .gap-2{gap:.5rem!important}ng-table-pg-skeleton .gap-4{gap:1rem!important}ng-table-pg-skeleton .justify-center{justify-content:center!important}ng-table-pg-skeleton .justify-between{justify-content:space-between!important}ng-table-pg-skeleton .items-center{align-items:center!important}ng-table-pg-skeleton .overflow-x-auto{overflow-x:auto!important}ng-table-pg-skeleton .min-w-full{min-width:100%!important}ng-table-pg-skeleton .w-full{width:100%!important}ng-table-pg-skeleton .hidden{display:none!important}@media (min-width: 640px){ng-table-pg-skeleton .sm\\:flex-row{flex-direction:row!important}ng-table-pg-skeleton .sm\\:justify-between{justify-content:space-between!important}ng-table-pg-skeleton .sm\\:items-center{align-items:center!important}ng-table-pg-skeleton .sm\\:justify-end{justify-content:flex-end!important}ng-table-pg-skeleton .sm\\:w-2\\/3{width:66.666667%!important}ng-table-pg-skeleton .sm\\:w-24{width:6rem!important}ng-table-pg-skeleton .sm\\:w-36{width:9rem!important}ng-table-pg-skeleton .sm\\:px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}}@media (min-width: 768px){ng-table-pg-skeleton .md\\:hidden{display:none!important}ng-table-pg-skeleton .md\\:table-header-group{display:table-header-group!important}ng-table-pg-skeleton .md\\:table-row{display:table-row!important}}ng-table-pg-skeleton .space-y-6>*+*{margin-top:1.5rem!important}ng-table-pg-skeleton .px-3{padding-left:.75rem!important;padding-right:.75rem!important}ng-table-pg-skeleton .py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}ng-table-pg-skeleton .py-4{padding-top:1rem!important;padding-bottom:1rem!important}ng-table-pg-skeleton .text-left{text-align:left!important}ng-table-pg-skeleton .bg-white{background-color:#fff!important}ng-table-pg-skeleton .bg-gray-50{background-color:#f9fafb!important}ng-table-pg-skeleton .rounded-lg{border-radius:.5rem!important}ng-table-pg-skeleton .rounded-full{border-radius:9999px!important}ng-table-pg-skeleton .shadow{box-shadow:0 1px 3px #0000001a,0 1px 2px -1px #0000001a!important}ng-table-pg-skeleton .overflow-hidden{overflow:hidden!important}ng-table-pg-skeleton .border-t{border-top-width:1px!important}ng-table-pg-skeleton .border-gray-200{border-color:#e5e7eb!important}ng-table-pg-skeleton .divide-y>*+*{border-top-width:1px!important}ng-table-pg-skeleton .divide-gray-200>*+*{border-color:#e5e7eb!important}ng-table-pg-skeleton .h-4{height:1rem!important}ng-table-pg-skeleton .h-6{height:1.5rem!important}ng-table-pg-skeleton .h-8{height:2rem!important}ng-table-pg-skeleton .h-10{height:2.5rem!important}ng-table-pg-skeleton .w-4{width:1rem!important}ng-table-pg-skeleton .w-6{width:1.5rem!important}ng-table-pg-skeleton .w-8{width:2rem!important}ng-table-pg-skeleton .w-12{width:3rem!important}ng-table-pg-skeleton .w-16{width:4rem!important}ng-table-pg-skeleton .w-20{width:5rem!important}ng-table-pg-skeleton .w-24{width:6rem!important}ng-table-pg-skeleton .w-28{width:7rem!important}ng-table-pg-skeleton .w-32{width:8rem!important}\n"], encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: SkeletonTableComponent, decorators: [{ type: Component, args: [{ selector: 'ng-table-pg-skeleton', standalone: true, encapsulation: ViewEncapsulation.None, template: ` <div class="space-y-6"> <!-- Search and buttons --> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4" > <div class="skeleton h-10 w-full sm:w-2/3"></div> <div class="flex flex-col sm:flex-row gap-2"> <div class="skeleton h-10 w-full sm:w-24"></div> <div class="skeleton h-10 w-full sm:w-36"></div> </div> </div> <!-- Table Container with overflow --> <div class="bg-white rounded-lg shadow overflow-hidden"> <div class="overflow-x-auto"> <table class="w-full min-w-full"> <!-- Desktop Headers (hidden on mobile) --> <thead class="bg-gray-50 hidden md:table-header-group"> <tr> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-8"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-16"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-12"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-20"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-24"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-20"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-16"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-12"></div> </th> <th class="px-3 sm:px-6 py-3 text-left"> <div class="skeleton h-4 w-8"></div> </th> </tr> </thead> <!-- Mobile Headers (visible only on mobile) --> <thead class="bg-gray-50 md:hidden"> <tr> <th class="px-3 py-3 text-left"> <div class="skeleton h-4 w-16"></div> </th> <th class="px-3 py-3 text-left"> <div class="skeleton h-4 w-20"></div> </th> <th class="px-3 py-3 text-left"> <div class="skeleton h-4 w-12"></div> </th> <th class="px-3 py-3 text-left"> <div class="skeleton h-4 w-8"></div> </th> </tr> </thead> <tbody class="divide-y divide-gray-200"> <!-- Desktop Rows (hidden on mobile) --> @for (row of [1,2,3,4,5,6,7,8,9,10]; track row) { <tr class="border-t border-gray-200 hidden md:table-row"> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-8"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-20"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-6 w-8 rounded-full"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-32"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-28"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-24"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-4 w-16"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-6 w-16 rounded-full"></div> </td> <td class="px-3 sm:px-6 py-4"> <div class="skeleton h-6 w-6"></div> </td> </tr> } <!-- Mobile Rows (visible only on mobile) --> @for (row of [1,2,3,4,5,6,7,8,9,10]; track row) { <tr class="border-t border-gray-200 md:hidden"> <td class="px-3 py-4"> <div class="skeleton h-4 w-16"></div> </td> <td class="px-3 py-4"> <div class="skeleton h-4 w-20"></div> </td> <td class="px-3 py-4"> <div class="skeleton h-6 w-16 rounded-full"></div> </td> <td class="px-3 py-4"> <div class="skeleton h-6 w-6"></div> </td> </tr> } </tbody> </table> </div> </div> <!-- Pagination --> <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4" > <div class="flex items-center gap-2"> <div class="skeleton h-4 w-12"></div> <div class="skeleton h-8 w-12"></div> <div class="skeleton h-4 w-16"></div> </div> <div class="flex items-center justify-center sm:justify-end gap-2"> <div class="skeleton h-8 w-8"></div> <div class="skeleton h-8 w-8"></div> <div class="skeleton h-8 w-8"></div> <div class="skeleton h-4 w-4"></div> <div class="skeleton h-8 w-8"></div> <div class="skeleton h-8 w-8"></div> </div> </div> </div> `, styles: ["ng-table-pg-skeleton .skeleton{background-color:#e5e7eb!important;animation:ng-skeleton-pulse 2s cubic-bezier(.4,0,.6,1) infinite!important;border-radius:.25rem!important}@keyframes ng-skeleton-pulse{0%,to{opacity:1}50%{opacity:.5}}ng-table-pg-skeleton .flex{display:flex!important}ng-table-pg-skeleton .flex-col{flex-direction:column!important}ng-table-pg-skeleton .gap-2{gap:.5rem!important}ng-table-pg-skeleton .gap-4{gap:1rem!important}ng-table-pg-skeleton .justify-center{justify-content:center!important}ng-table-pg-skeleton .justify-between{justify-content:space-between!important}ng-table-pg-skeleton .items-center{align-items:center!important}ng-table-pg-skeleton .overflow-x-auto{overflow-x:auto!important}ng-table-pg-skeleton .min-w-full{min-width:100%!important}ng-table-pg-skeleton .w-full{width:100%!important}ng-table-pg-skeleton .hidden{display:none!important}@media (min-width: 640px){ng-table-pg-skeleton .sm\\:flex-row{flex-direction:row!important}ng-table-pg-skeleton .sm\\:justify-between{justify-content:space-between!important}ng-table-pg-skeleton .sm\\:items-center{align-items:center!important}ng-table-pg-skeleton .sm\\:justify-end{justify-content:flex-end!important}ng-table-pg-skeleton .sm\\:w-2\\/3{width:66.666667%!important}ng-table-pg-skeleton .sm\\:w-24{width:6rem!important}ng-table-pg-skeleton .sm\\:w-36{width:9rem!important}ng-table-pg-skeleton .sm\\:px-6{padding-left:1.5rem!important;padding-right:1.5rem!important}}@media (min-width: 768px){ng-table-pg-skeleton .md\\:hidden{display:none!important}ng-table-pg-skeleton .md\\:table-header-group{display:table-header-group!important}ng-table-pg-skeleton .md\\:table-row{display:table-row!important}}ng-table-pg-skeleton .space-y-6>*+*{margin-top:1.5rem!important}ng-table-pg-skeleton .px-3{padding-left:.75rem!important;padding-right:.75rem!important}ng-table-pg-skeleton .py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}ng-table-pg-skeleton .py-4{padding-top:1rem!important;padding-bottom:1rem!important}ng-table-pg-skeleton .text-left{text-align:left!important}ng-table-pg-skeleton .bg-white{background-color:#fff!important}ng-table-pg-skeleton .bg-gray-50{background-color:#f9fafb!important}ng-table-pg-skeleton .rounded-lg{border-radius:.5rem!important}ng-table-pg-skeleton .rounded-full{border-radius:9999px!important}ng-table-pg-skeleton .shadow{box-shadow:0 1px 3px #0000001a,0 1px 2px -1px #0000001a!important}ng-table-pg-skeleton .overflow-hidden{overflow:hidden!important}ng-table-pg-skeleton .border-t{border-top-width:1px!important}ng-table-pg-skeleton .border-gray-200{border-color:#e5e7eb!important}ng-table-pg-skeleton .divide-y>*+*{border-top-width:1px!important}ng-table-pg-skeleton .divide-gray-200>*+*{border-color:#e5e7eb!important}ng-table-pg-skeleton .h-4{height:1rem!important}ng-table-pg-skeleton .h-6{height:1.5rem!important}ng-table-pg-skeleton .h-8{height:2rem!important}ng-table-pg-skeleton .h-10{height:2.5rem!important}ng-table-pg-skeleton .w-4{width:1rem!important}ng-table-pg-skeleton .w-6{width:1.5rem!important}ng-table-pg-skeleton .w-8{width:2rem!important}ng-table-pg-skeleton .w-12{width:3rem!important}ng-table-pg-skeleton .w-16{width:4rem!important}ng-table-pg-skeleton .w-20{width:5rem!important}ng-table-pg-skeleton .w-24{width:6rem!important}ng-table-pg-skeleton .w-28{width:7rem!important}ng-table-pg-skeleton .w-32{width:8rem!important}\n"] }] }] }); class DropdownButtonComponent { elementRef; cdr; dropdownButton; dropdownContent; buttonClass = 'text-gray-400 hover:text-gray-600'; isOpen = false; opened = new EventEmitter(); closed = new EventEmitter(); dropdownStyle = { top: `-9999px`, left: `-9999px`, }; destroy$ = new Subject(); constructor(elementRef, cdr) { this.elementRef = elementRef; this.cdr = cdr; } ngAfterViewInit() { this.updateDropdownPosition(); // Initial position } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } clickOutside(event) { if (!this.elementRef.nativeElement.contains(event.target)) this.closeDropdown(); } onScroll() { if (this.isOpen) { this.updateDropdownPosition(); } } toggleDropdown(event) { event.stopPropagation(); this.isOpen ? this.closeDropdown() : this.openDropdown(); } openDropdown() { this.opened.emit(); this.isOpen = true; setTimeout(() => this.updateDropdownPosition(), 10); } closeDropdown() { this.dropdownStyle = { top: `-9999px`, left: `-9999px`, }; this.isOpen = false; this.closed.emit(); } updateDropdownPosition() { if (!this.dropdownButton || !this.dropdownContent) return; const buttonRect = this.dropdownButton.nativeElement.getBoundingClientRect(); const dropdownRect = this.dropdownContent.nativeElement.getBoundingClientRect(); const scrollableContainer = this.findScrollableContainer(this.dropdownButton.nativeElement); let top = buttonRect.bottom + 10; let left = buttonRect.right - dropdownRect.width; if (scrollableContainer) { const containerRect = scrollableContainer.getBoundingClientRect(); if (top + dropdownRect.height > window.innerHeight) { top = buttonRect.top - dropdownRect.height - 10; if (top < 10) top = 80; } if (top + dropdownRect.height <= window.innerHeight && top + dropdownRect.height > containerRect.bottom) { top = buttonRect.top - dropdownRect.height - 10; if (top < containerRect.top) top = containerRect.top; } if (left < containerRect.left) { left = containerRect.left; } } else { if (top + dropdownRect.height > window.innerHeight) { top = buttonRect.top - dropdownRect.height - 10; if (top < 10) top = 80; } if (left < 0) { left = 0; } if (left + dropdownRect.width > window.innerWidth) { left = window.innerWidth - dropdownRect.width; } } this.dropdownStyle = { top: `${top}px`, left: `${left}px`, overflowY: 'auto', overflowX: 'hidden', }; this.cdr.detectChanges(); } findScrollableContainer(element) { while (element && element !== document.body) { const style = window.getComputedStyle(element); const overflow = style.getPropertyValue('overflow') + style.getPropertyValue('overflow-y') + style.getPropertyValue('overflow-x'); if (/(auto|scroll)/.test(overflow) && (element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth)) { return element; } element = element.parentElement; } return null; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: DropdownButtonComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.5", type: DropdownButtonComponent, isStandalone: true, selector: "app-dropdown-button", inputs: { buttonClass: "buttonClass", isOpen: "isOpen" }, outputs: { opened: "opened", closed: "closed" }, host: { listeners: { "document:click": "clickOutside($event)", "window:scroll": "onScroll()" } }, viewQueries: [{ propertyName: "dropdownButton", first: true, predicate: ["dropdownButton"], descendants: true }, { propertyName: "dropdownContent", first: true, predicate: ["dropdownContent"], descendants: true }], ngImport: i0, template: ` <div class="relative inline-block"> <button #dropdownButton (click)="toggleDropdown($event)" [class]="buttonClass" > <ng-content select="[buttonContent]"></ng-content> </button> <div *ngIf="isOpen" #dropdownContent [ngStyle]="dropdownStyle" [ngClass]="{ invisible: dropdownStyle.top == '-9999px' }" class="fixed rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50" (click)="$event.stopPropagation()" > <div class="py-1"> <ng-content select="[dropdownContent]"></ng-content> </div> </div> </div> `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: DropdownButtonComponent, decorators: [{ type: Component, args: [{ selector: 'app-dropdown-button', standalone: true, imports: [CommonModule], template: ` <div class="relative inline-block"> <button #dropdownButton (click)="toggleDropdown($event)" [class]="buttonClass" > <ng-content select="[buttonContent]"></ng-content> </button> <div *ngIf="isOpen" #dropdownContent [ngStyle]="dropdownStyle" [ngClass]="{ invisible: dropdownStyle.top == '-9999px' }" class="fixed rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-50" (click)="$event.stopPropagation()" > <div class="py-1"> <ng-content select="[dropdownContent]"></ng-content> </div> </div> </div> `, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }], propDecorators: { dropdownButton: [{ type: ViewChild, args: ['dropdownButton'] }], dropdownContent: [{ type: ViewChild, args: ['dropdownContent'] }], buttonClass: [{ type: Input }], isOpen: [{ type: Input }], opened: [{ type: Output }], closed: [{ type: Output }], clickOutside: [{ type: HostListener, args: ['document:click', ['$event']] }], onScroll: [{ type: HostListener, args: ['window:scroll'] }] } }); class TableComponent { translate; data = [];