289 lines
12 KiB
HTML
289 lines
12 KiB
HTML
<!-- Start::row-1 -->
|
|
<div class="grid grid-cols-12 data-table-modern">
|
|
<div class="xl:col-span-12 col-span-12">
|
|
<div class="box overflow-visible">
|
|
<div class="box-header justify-between items-center gap-3">
|
|
<h5 class="box-title">
|
|
{{ tableTitle () }}
|
|
</h5>
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
@if(showFilterButton()){
|
|
<div animate.enter="filter-btn-enter" animate.leave="filter-btn-leave" class="flex items-center">
|
|
<app-button action="custom" icon="ti ti-filter" label="Filter" size="sm" iconClass="!text-[1rem]"
|
|
[variant]="filterActive() ? 'primary-full' : 'outline-primary'"
|
|
className="!rounded-full shadow-sm !whitespace-nowrap !mb-0" (buttonClicked)="onFilterClick($event)" />
|
|
</div>
|
|
}
|
|
@if(showImportExportButtons()){
|
|
<div class="flex items-center gap-2">
|
|
<app-button action="custom" icon="ri-upload-2-line" label="Import CSV" variant="outline-primary" size="sm"
|
|
className="!rounded-full shadow-sm !mb-0" (buttonClicked)="onImportClick($event)" />
|
|
<app-button action="custom" icon="ri-download-2-line" label="Export" variant="secondary" size="sm"
|
|
className="!rounded-full shadow-sm !mb-0" (buttonClicked)="onExportClick($event)" />
|
|
</div>
|
|
}
|
|
@if(showAddButton()){
|
|
<div class="flex items-center">
|
|
<app-button action="add" [label]="buttonTitle()" size="sm" iconClass="!text-[1rem]"
|
|
className="!rounded-full shadow-sm !mb-0" (buttonClicked)="onAddClick($event)" />
|
|
|
|
</div>
|
|
}
|
|
<!-- @if (toolbarTemplate(); as toolbar) {
|
|
<div>
|
|
<ng-container [ngTemplateOutlet]="toolbar.templateRef" />
|
|
</div>
|
|
} -->
|
|
@if (showSearch()) {
|
|
<div class="search-wrapper flex items-center">
|
|
<i class="ri-search-line search-icon"></i>
|
|
<input #searchInput class="form-control form-control-sm brdr-clr" type="text"
|
|
[placeholder]="searchPlaceholder()" aria-label="table search" (input)="onSearch(searchInput.value)">
|
|
</div>
|
|
}
|
|
|
|
</div>
|
|
</div>
|
|
|
|
@if (toolbarTemplate(); as toolbar) {
|
|
<div class="table-filter-row" [class.is-open]="filterActive()">
|
|
<div class="table-filter-row-inner">
|
|
<div class="table-filter-row-content" #filterRowContent>
|
|
<ng-container [ngTemplateOutlet]="toolbar.templateRef" />
|
|
@if (filterActionsTemplate(); as filterActions) {
|
|
<div class="table-filter-row-actions" [class.is-pinned]="filterRowOverflowing()">
|
|
<ng-container [ngTemplateOutlet]="filterActions.templateRef" />
|
|
</div>
|
|
}
|
|
</div>
|
|
<button type="button"
|
|
class="ti-btn ti-btn-icon ti-btn-sm ti-btn-light !rounded-full !m-0 shrink-0 filter-close-btn"
|
|
aria-label="Close filter" (click)="onFilterClick($event)">
|
|
<i class="ti ti-x"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="box-body !p-0 modern-table-body">
|
|
<div class="table-responsive overflow-x-auto overflow-y-visible modern-table-wrapper">
|
|
<table [class]="tableClass()">
|
|
<thead>
|
|
<tr [class]="trHeadClass()">
|
|
@for (column of columns(); track column.key) {
|
|
<th scope="col" [class]="getHeaderClass(column)" [style.width]="column.width || null"
|
|
(click)="onSort(column)">
|
|
<span class="inline-flex items-center gap-1" [class.cursor-pointer]="column.sortable">
|
|
{{ column.label }}
|
|
|
|
@if (column.sortable) {
|
|
<i [class]="getSortIconClass(column) + ' text-xs align-middle'"></i>
|
|
}
|
|
</span>
|
|
</th>
|
|
}
|
|
|
|
@if (actions().length > 0) {
|
|
<th scope="col" [class]="composeClass(actionHeaderClass(), 'relative overflow-visible')">Action</th>
|
|
}
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody>
|
|
@if (loading()) {
|
|
<tr class="border-b border-defaultborder">
|
|
<td [attr.colspan]="totalVisibleColumns()" class="py-10">
|
|
<div class="flex flex-col items-center justify-center gap-3 text-center">
|
|
<i class="ti ti-loader-2 animate-spin text-[1.5rem]"></i>
|
|
<span class="text-[0.875rem]">Loading data...</span>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
} @else if (rows().length === 0) {
|
|
<tr class="border-b border-defaultborder bg-light/30 dark:bg-black/10">
|
|
<td [attr.colspan]="totalVisibleColumns()" class="p-0">
|
|
<div class="flex min-h-[140px] flex-col items-center justify-center px-4 py-8 text-center">
|
|
<div
|
|
class="mb-3 inline-flex h-11 w-11 items-center justify-center rounded-full bg-primary/10 text-primary dark:bg-primary/15">
|
|
<i class="ti ti-database-off text-[1.25rem]" aria-hidden="true"></i>
|
|
</div>
|
|
|
|
<p class="text-sm font-semibold text-defaulttextcolor dark:text-white/80">
|
|
{{ emptyMessage() }}
|
|
</p>
|
|
|
|
@if (emptyDescription()) {
|
|
<p class="mt-1 max-w-md text-sm text-textmuted">
|
|
{{ emptyDescription() }}
|
|
</p>
|
|
}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
} @else
|
|
{
|
|
@for (row of rows(); track row['id'] ?? $index; let rowIndex = $index) {
|
|
<tr [class]="getRowClass(row, $index)" (click)="onRowClick(row)">
|
|
@for (column of columns(); track column.key) {
|
|
<td [class]="getCellClass(column)">
|
|
@if (getCellTemplate(column); as customTemplate) {
|
|
<ng-container [ngTemplateOutlet]="customTemplate.templateRef"
|
|
[ngTemplateOutletContext]="getCellTemplateContext(column, row)" />
|
|
} @else if (column.badge) {
|
|
<span [class]="getBadgeClass(column, row)">
|
|
{{ getDisplayValue(column, row) }}
|
|
</span>
|
|
} @else {
|
|
{{ getDisplayValue(column, row) }}
|
|
}
|
|
</td>
|
|
}
|
|
|
|
@if (actions().length > 0) {
|
|
<td [class]="composeClass(actionCellClass(), 'overflow-visible')" (click)="$event.stopPropagation()">
|
|
<div class="inline-flex w-full justify-center">
|
|
|
|
<button type="button" cdkOverlayOrigin #actionMenuOrigin="cdkOverlayOrigin" data-action-menu-trigger
|
|
class="
|
|
ti-btn ti-btn-icon ti-btn-sm ti-btn-primary
|
|
!m-0 !h-6 !w-6 !p-0
|
|
" aria-label="Open row actions" aria-haspopup="menu" [attr.aria-controls]="getActionMenuId(row)"
|
|
[attr.aria-expanded]="isActionMenuOpen(row)" (click)="toggleActionMenu($event, row)">
|
|
<i class="ri-more-2-fill text-sm"></i>
|
|
</button>
|
|
|
|
<ng-template cdkConnectedOverlay [cdkConnectedOverlayOrigin]="actionMenuOrigin"
|
|
[cdkConnectedOverlayOpen]="isActionMenuOpen(row)"
|
|
[cdkConnectedOverlayPositions]="actionMenuPositions" [cdkConnectedOverlayViewportMargin]="8"
|
|
[cdkConnectedOverlayPush]="true" [cdkConnectedOverlayHasBackdrop]="false"
|
|
(overlayOutsideClick)="onActionMenuOutsideClick($event)">
|
|
<div [id]="getActionMenuId(row)" role="menu" class="
|
|
w-[170px]
|
|
max-w-[calc(100vw-16px)]
|
|
overflow-hidden
|
|
rounded-lg
|
|
border border-defaultborder
|
|
bg-white
|
|
p-1.5
|
|
shadow-xl
|
|
dark:border-defaultborder/10
|
|
dark:bg-bodybg
|
|
" (click)="$event.stopPropagation()">
|
|
@for (action of actions(); track $index) {
|
|
@if (isActionVisible(action, row)) {
|
|
<button type="button" role="menuitem" class="
|
|
flex w-full items-center gap-2
|
|
rounded-md px-3 py-2
|
|
text-start text-sm
|
|
transition-colors
|
|
hover:bg-light
|
|
dark:hover:bg-white/10
|
|
" [disabled]="isActionDisabled(action, row)" [class.opacity-50]="isActionDisabled(action, row)"
|
|
[class.cursor-not-allowed]="isActionDisabled(action, row)"
|
|
[class.!text-danger]="action.type === 'delete'"
|
|
(click)="onDropdownActionClick($event, action, row)">
|
|
@if (action.icon) {
|
|
<i [class]="action.icon"></i>
|
|
}
|
|
|
|
<span>{{ action.label }}</span>
|
|
</button>
|
|
}
|
|
}
|
|
</div>
|
|
</ng-template>
|
|
|
|
</div>
|
|
</td>
|
|
}
|
|
</tr>
|
|
}
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
@if (showPaginator()) {
|
|
<div class="box-footer border-t-0 modern-table-footer">
|
|
<div class="flex items-center justify-between flex-wrap gap-3">
|
|
|
|
<!-- Record details and page-size selection -->
|
|
<div class="flex items-center flex-wrap gap-3">
|
|
|
|
<div class="text-sm text-defaulttextcolor">
|
|
Showing
|
|
<b>{{ startRecord() }}</b>
|
|
to
|
|
<b>{{ endRecord() }}</b>
|
|
of
|
|
<b>{{ totalRecords() }}</b>
|
|
entries
|
|
</div>
|
|
|
|
@if (pageSizeOptions().length > 0) {
|
|
<div class="flex items-center gap-2">
|
|
<label for="dataTablePageSize" class="text-sm whitespace-nowrap text-defaulttextcolor">
|
|
Show
|
|
</label>
|
|
|
|
<select id="dataTablePageSize"
|
|
class="form-select form-select-sm !w-[80px] !py-1 !ps-2.5 !pe-6 text-defaulttextcolor bg-white dark:bg-bodybg border border-defaultborder rounded-md"
|
|
[value]="pageSize()"
|
|
(change)="onPageSizeChange($event)">
|
|
@for (size of pageSizeOptions(); track size) {
|
|
<option [value]="size" [selected]="size === pageSize()" class="text-defaulttextcolor bg-white dark:bg-bodybg">
|
|
{{ size }}
|
|
</option>
|
|
}
|
|
</select>
|
|
|
|
<span class="text-sm whitespace-nowrap text-defaulttextcolor">
|
|
entries
|
|
</span>
|
|
</div>
|
|
}
|
|
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<nav aria-label="Table page navigation">
|
|
<ul class="ti-pagination mb-0">
|
|
|
|
<!-- Previous -->
|
|
<li class="page-item" [class.disabled]="pageIndex() <= 1">
|
|
<button type="button" class="page-link px-3 py-[0.375rem]" [disabled]="pageIndex() <= 1"
|
|
(click)="goToPage(pageIndex() - 1)">
|
|
Previous
|
|
</button>
|
|
</li>
|
|
|
|
<!-- Page numbers -->
|
|
@for (page of visiblePages(); track page) {
|
|
<li class="page-item" [class.active]="page === pageIndex()">
|
|
<button type="button" class="page-link px-3 py-[0.375rem]" [class.active]="page === pageIndex()"
|
|
[attr.aria-current]="
|
|
page === pageIndex() ? 'page' : null
|
|
" (click)="goToPage(page)">
|
|
{{ page }}
|
|
</button>
|
|
</li>
|
|
}
|
|
|
|
<!-- Next -->
|
|
<li class="page-item" [class.disabled]="pageIndex() >= totalPages()">
|
|
<button type="button" class="page-link px-3 py-[0.375rem]" [disabled]="pageIndex() >= totalPages()"
|
|
(click)="goToPage(pageIndex() + 1)">
|
|
Next
|
|
</button>
|
|
</li>
|
|
|
|
</ul>
|
|
</nav>
|
|
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!--End::row-1 --> |