Files
2026-09-21 10:14:57 +01:00

2223 lines
158 KiB
PHP
Executable File

@php
use Filament\Support\Enums\Alignment;
use Filament\Support\Enums\VerticalAlignment;
use Filament\Support\Enums\Width;
use Filament\Support\Facades\FilamentView;
use Filament\Tables\Actions\HeaderActionsPosition;
use Filament\Tables\Columns\Column;
use Filament\Tables\Columns\ColumnGroup;
use Filament\Tables\Enums\ColumnManagerLayout;
use Filament\Tables\Enums\ColumnManagerResetActionPosition;
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Enums\FiltersResetActionPosition;
use Filament\Tables\Enums\RecordActionsPosition;
use Filament\Tables\Enums\RecordCheckboxPosition;
use Filament\Tables\View\TablesRenderHook;
use Illuminate\Support\Str;
use Illuminate\View\ComponentAttributeBag;
$defaultRecordActions = $getRecordActions();
$flatRecordActionsCount = count($getFlatRecordActions());
$recordActionsAlignment = $getRecordActionsAlignment();
$recordActionsPosition = $getRecordActionsPosition();
$recordActionsColumnLabel = $getRecordActionsColumnLabel();
if (!$recordActionsAlignment instanceof Alignment) {
$recordActionsAlignment = filled($recordActionsAlignment)
? Alignment::tryFrom($recordActionsAlignment) ?? $recordActionsAlignment
: null;
}
$activeFiltersCount = $getActiveFiltersCount();
$isSelectionDisabled = $isSelectionDisabled();
$maxSelectableRecords = $getMaxSelectableRecords();
$columns = $getVisibleColumns();
$collapsibleColumnsLayout = $getCollapsibleColumnsLayout();
$columnsLayout = $getColumnsLayout();
$content = $getContent();
$contentGrid = $getContentGrid();
$contentFooter = $getContentFooter();
$filterIndicators = $getFilterIndicators();
$filtersApplyAction = $getFiltersApplyAction();
$filtersForm = $getFiltersForm();
$filtersFormWidth = $getFiltersFormWidth();
$filtersResetActionPosition = $getFiltersResetActionPosition();
$columnManagerResetActionPosition = $getColumnManagerResetActionPosition();
$hasColumnGroups = $hasColumnGroups();
$hasColumnsLayout = $hasColumnsLayout();
$hasPageSummary = $hasPageSummary();
$hasAllTableSummary = $hasAllTableSummary();
$hasSummary = $hasSummary($this->getAllTableSummaryQuery());
$hasTopLevelSummary = $hasSummary && ($hasPageSummary || $hasAllTableSummary);
$header = $getHeader();
$headerActions = array_filter(
$getHeaderActions(),
fn(\Filament\Actions\Action|\Filament\Actions\ActionGroup $action): bool => $action->isVisible(),
);
$headerActionsPosition = $getHeaderActionsPosition();
$heading = $getHeading();
$group = $getGrouping();
$toolbarActions = array_filter(
$getToolbarActions(),
fn(\Filament\Actions\Action|\Filament\Actions\ActionGroup $action): bool => $action->isVisible(),
);
$hasNonBulkToolbarAction = false;
foreach ($toolbarActions as $toolbarAction) {
if ($toolbarAction instanceof \Filament\Actions\BulkActionGroup) {
continue;
}
if ($toolbarAction instanceof \Filament\Actions\ActionGroup) {
if ($toolbarAction->hasNonBulkAction()) {
$hasNonBulkToolbarAction = true;
break;
}
continue;
}
if (!$toolbarAction->isBulk()) {
$hasNonBulkToolbarAction = true;
break;
}
}
$groups = $getGroups();
$description = $getDescription();
$isGroupsOnly = $isGroupsOnly() && $group;
$isReorderable = $isReorderable();
$isReordering = $isReordering();
$areGroupingSettingsVisible = !$isReordering && count($groups) && !$areGroupingSettingsHidden();
$isGroupingDirectionSettingHidden = $isGroupingDirectionSettingHidden();
$areGroupsCollapsedByDefault = $areGroupsCollapsedByDefault();
$areGroupingSettingsInDropdownOnDesktop = $areGroupingSettingsInDropdownOnDesktop();
$isColumnSearchVisible = $isSearchableByColumn();
$isGlobalSearchVisible = $isSearchable();
$isSearchOnBlur = $isSearchOnBlur();
$isSelectionEnabled = $isSelectionEnabled() && !$isGroupsOnly;
$selectsCurrentPageOnly = $selectsCurrentPageOnly();
$selectsGroupsOnly = $selectsGroupsOnly();
$recordCheckboxPosition = $getRecordCheckboxPosition();
$isStriped = $isStriped();
$isStackedOnMobile = $isStackedOnMobile();
$isLoaded = $isLoaded();
$hasFilters = $isFilterable();
$filtersLayout = $getFiltersLayout();
$filtersTriggerAction = $getFiltersTriggerAction();
$hasFiltersDialog = $hasFilters && in_array($filtersLayout, [FiltersLayout::Dropdown, FiltersLayout::Modal]);
$hasFiltersAboveContent =
$hasFilters && in_array($filtersLayout, [FiltersLayout::AboveContent, FiltersLayout::AboveContentCollapsible]);
$hasFiltersBelowContent = $hasFilters && $filtersLayout === FiltersLayout::BelowContent;
$hasFiltersBeforeContent =
$hasFilters &&
in_array($filtersLayout, [FiltersLayout::BeforeContent, FiltersLayout::BeforeContentCollapsible]);
$hasFiltersAfterContent =
$hasFilters && in_array($filtersLayout, [FiltersLayout::AfterContent, FiltersLayout::AfterContentCollapsible]);
$hasCollapsibleFilters =
$hasFilters &&
in_array($filtersLayout, [
FiltersLayout::AboveContentCollapsible,
FiltersLayout::BeforeContentCollapsible,
FiltersLayout::AfterContentCollapsible,
]);
$hasFiltersTrigger = $hasFilters && ($hasFiltersDialog || $hasFiltersBeforeContent || $hasFiltersAfterContent);
$filtersFormMaxHeight = $getFiltersFormMaxHeight();
$hasColumnManager = $hasColumnManager();
$columnManagerLayout = $getColumnManagerLayout();
$hasReorderableColumns = $hasReorderableColumns();
$hasToggleableColumns = $hasToggleableColumns();
$columnManagerApplyAction = $getColumnManagerApplyAction();
$columnManagerTriggerAction = $getColumnManagerTriggerAction();
$hasHeader =
$header ||
$heading ||
$description ||
($headerActions && !$isReordering) ||
$isReorderable ||
$areGroupingSettingsVisible ||
$isGlobalSearchVisible ||
$hasFilters ||
count($filterIndicators) ||
$hasColumnManager;
$hasHeaderToolbar =
$isReorderable ||
$areGroupingSettingsVisible ||
$isGlobalSearchVisible ||
$hasFiltersTrigger ||
$hasColumnManager;
// https://github.com/filamentphp/filament/pull/19787
$headerVisibilityMode =
$hasHeader || $hasNonBulkToolbarAction ? 'visible' : (count($toolbarActions) ? 'selection' : 'hidden');
$headerToolbarVisibilityMode =
$hasHeaderToolbar || $hasNonBulkToolbarAction ? 'visible' : (count($toolbarActions) ? 'selection' : 'hidden');
$headingTag = $getHeadingTag();
$secondLevelHeadingTag = $heading ? $getHeadingTag(1) : $headingTag;
$pluralModelLabel = $getPluralModelLabel();
$records = $isLoaded ? $getRecords() : null;
$hasPagination =
($records instanceof \Illuminate\Contracts\Pagination\Paginator ||
$records instanceof \Illuminate\Contracts\Pagination\CursorPaginator) &&
($records instanceof \Illuminate\Contracts\Pagination\LengthAwarePaginator
? $records->total()
: $records->isNotEmpty());
$hasEmptyState = $records !== null && !count($records);
$hasContentLayout = $content || $hasColumnsLayout;
$searchDebounce = $getSearchDebounce();
$allSelectableRecordsCount = $isSelectionEnabled && $isLoaded ? $getAllSelectableRecordsCount() : null;
$columnsCount = count($columns);
$reorderRecordsTriggerAction = $getReorderRecordsTriggerAction($isReordering);
$page = $this->getTablePage();
$defaultSortOptionLabel = $getDefaultSortOptionLabel();
$sortDirection = $getSortDirection();
if (count($defaultRecordActions) && !$isReordering) {
$columnsCount++;
}
if ($isSelectionEnabled || $isReordering) {
$columnsCount++;
}
if ($group) {
$groupedSummarySelectedState = $this->getTableSummarySelectedState(
$this->getAllTableSummaryQuery(),
modifyQueryUsing: fn(\Illuminate\Database\Query\Builder $query) => $group->groupQuery(
$query,
model: $getQuery()->getModel(),
),
);
}
if (is_string($filtersFormWidth)) {
$filtersFormWidth = Width::tryFrom($filtersFormWidth) ?? $filtersFormWidth;
}
@endphp
<div @if (!$isLoaded) wire:init="loadTable" @endif x-data="filamentTable({
areGroupsCollapsedByDefault: @js($areGroupsCollapsedByDefault),
canTrackDeselectedRecords: @js($canTrackDeselectedRecords()),
currentSelectionLivewireProperty: @js($getCurrentSelectionLivewireProperty()),
maxSelectableRecords: @js($maxSelectableRecords),
selectsCurrentPageOnly: @js($selectsCurrentPageOnly),
$wire,
})"
{{ $getExtraAttributeBag()->class(['fi-ta', 'fi-loading' => $records === null]) }}>
<input type="hidden" value="{{ $allSelectableRecordsCount }}" x-ref="allSelectableRecordsCount" />
<div @class([
'fi-ta-ctn',
'fi-ta-ctn-with-content-layout' => $hasContentLayout,
'fi-ta-ctn-with-footer' =>
$hasPagination || $hasEmptyState || $hasFiltersBelowContent,
'fi-ta-ctn-with-header' => $hasHeader,
])>
@if ($hasFiltersBeforeContent)
<div wire:ignore.self x-ref="filtersContentContainer" x-transition:enter-start="fi-opacity-0"
x-transition:leave-end="fi-opacity-0" x-bind:class="{ 'fi-open': areFiltersOpen }"
@class([
'fi-ta-filters-before-content-ctn',
'lg:fi-open' => !$hasCollapsibleFilters,
($filtersFormWidth ??= Width::ExtraSmall) instanceof Width
? "fi-width-{$filtersFormWidth->value}"
: (is_string($filtersFormWidth)
? $filtersFormWidth
: null),
])>
<x-filament-tables::filters :apply-action="$filtersApplyAction" :form="$filtersForm" :heading-tag="$secondLevelHeadingTag"
class="fi-ta-filters-before-content" :reset-action-position="$filtersResetActionPosition" />
</div>
@endif
<div class="fi-ta-main">
<div @if (!$hasHeader) x-cloak @endif
x-show="@js($hasHeader) || @js($hasNonBulkToolbarAction) || (getSelectedRecordsCount() && @js(count($toolbarActions)))"
wire:key="{{ $this->getId() }}.table.header.{{ $headerVisibilityMode }}" class="fi-ta-header-ctn">
{{ FilamentView::renderHook(TablesRenderHook::HEADER_BEFORE, scopes: static::class) }}
@if ($header)
{{ $header }}
@elseif (($heading || $description || $headerActions) && !$isReordering)
<div @class([
'fi-ta-header',
'fi-ta-header-adaptive-actions-position' =>
$headerActions &&
$headerActionsPosition === HeaderActionsPosition::Adaptive,
])>
@if ($heading || $description)
<div>
@if ($heading)
<{{ $headingTag }} class="fi-ta-header-heading">
{{ $heading }}
</{{ $headingTag }}>
@endif
@if ($description)
<p class="fi-ta-header-description">
{{ $description }}
</p>
@endif
</div>
@endif
@if (!$isReordering && $headerActions)
<div class="fi-ta-actions fi-align-start fi-wrapped">
@foreach ($headerActions as $action)
{{ $action }}
@endforeach
</div>
@endif
</div>
@endif
{{ FilamentView::renderHook(TablesRenderHook::HEADER_AFTER, scopes: static::class) }}
@if ($hasFiltersAboveContent)
<div @if ($hasCollapsibleFilters) x-bind:class="{ 'fi-open': areFiltersOpen }" @endif
@class(['fi-ta-filters-above-content-ctn'])>
<x-filament-tables::filters :apply-action="$filtersApplyAction" :form="$filtersForm" :heading-tag="$secondLevelHeadingTag" x-cloak
:x-show="$hasCollapsibleFilters ? 'areFiltersOpen' : null" :reset-action-position="$filtersResetActionPosition" />
@if ($hasCollapsibleFilters)
<span x-on:click="areFiltersOpen = ! areFiltersOpen"
class="fi-ta-filters-trigger-action-ctn">
{{ $filtersTriggerAction->badge($activeFiltersCount) }}
</span>
@endif
</div>
@endif
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_BEFORE, scopes: static::class) }}
<div @if (!$hasHeaderToolbar) x-cloak @endif
x-show="@js($hasHeaderToolbar) || @js($hasNonBulkToolbarAction) || (getSelectedRecordsCount() && @js(count($toolbarActions)))"
wire:key="{{ $this->getId() }}.table.header-toolbar.{{ $headerToolbarVisibilityMode }}"
class="fi-ta-header-toolbar">
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_START, scopes: static::class) }}
<div class="fi-ta-actions fi-align-start fi-wrapped">
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_REORDER_TRIGGER_BEFORE, scopes: static::class) }}
@if ($isReorderable)
{{ $reorderRecordsTriggerAction }}
@endif
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_REORDER_TRIGGER_AFTER, scopes: static::class) }}
@if (!$isReordering && count($toolbarActions))
@foreach ($toolbarActions as $action)
{{ $action }}
@endforeach
@endif
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_GROUPING_SELECTOR_BEFORE, scopes: static::class) }}
@if ($areGroupingSettingsVisible)
<div x-data="{
grouping: $wire.$entangle('tableGrouping', true),
group: null,
direction: null,
}" x-init="if (grouping) {
;
[group, direction] = grouping.split(':')
direction ??= 'asc'
}
$watch('grouping', function() {
if (!grouping) {
group = null
direction = null
return
}
;
[group, direction] = grouping.split(':')
direction ??= 'asc'
})
$watch('direction', function() {
grouping = group ? `${group}:${direction}` : null
})
$watch('group', function(newGroup, oldGroup) {
if (!newGroup) {
direction = null
grouping = group ? `${group}:${direction}` : null
return
}
if (oldGroup) {
grouping = group ? `${group}:${direction}` : null
return
}
direction ??= 'asc'
grouping = group ? `${group}:${direction}` : null
})" class="fi-ta-grouping-settings">
<x-filament::dropdown placement="bottom-start" shift width="xs"
wire:key="{{ $this->getId() }}.table.grouping" @class([
'sm:fi-hidden' => !$areGroupingSettingsInDropdownOnDesktop,
])>
<x-slot name="trigger">
{{ $getGroupRecordsTriggerAction() }}
</x-slot>
<div class="fi-ta-grouping-settings-fields">
<label>
<span>
{{ __('filament-tables::table.grouping.fields.group.label') }}
</span>
<x-filament::input.wrapper>
<x-filament::input.select x-model="group"
x-on:change="resetCollapsedGroups()">
<option value="">-</option>
@foreach ($groups as $groupOption)
<option value="{{ $groupOption->getId() }}">
{{ $groupOption->getLabel() }}
</option>
@endforeach
</x-filament::input.select>
</x-filament::input.wrapper>
</label>
@if (!$isGroupingDirectionSettingHidden)
<label x-cloak x-show="group">
<span>
{{ __('filament-tables::table.grouping.fields.direction.label') }}
</span>
<x-filament::input.wrapper>
<x-filament::input.select x-model="direction">
<option value="asc">
{{ __('filament-tables::table.grouping.fields.direction.options.asc') }}
</option>
<option value="desc">
{{ __('filament-tables::table.grouping.fields.direction.options.desc') }}
</option>
</x-filament::input.select>
</x-filament::input.wrapper>
</label>
@endif
</div>
</x-filament::dropdown>
@if (!$areGroupingSettingsInDropdownOnDesktop)
<div class="fi-ta-grouping-settings-fields">
<label>
<x-filament::input.wrapper :prefix="__('filament-tables::table.grouping.fields.group.label')">
<x-filament::input.select x-model="group"
x-on:change="resetCollapsedGroups()">
<option value="">-</option>
@foreach ($groups as $groupOption)
<option value="{{ $groupOption->getId() }}">
{{ $groupOption->getLabel() }}
</option>
@endforeach
</x-filament::input.select>
</x-filament::input.wrapper>
</label>
@if (!$isGroupingDirectionSettingHidden)
<label x-cloak x-show="group">
<span class="fi-sr-only">
{{ __('filament-tables::table.grouping.fields.direction.label') }}
</span>
<x-filament::input.wrapper>
<x-filament::input.select x-model="direction">
<option value="asc">
{{ __('filament-tables::table.grouping.fields.direction.options.asc') }}
</option>
<option value="desc">
{{ __('filament-tables::table.grouping.fields.direction.options.desc') }}
</option>
</x-filament::input.select>
</x-filament::input.wrapper>
</label>
@endif
</div>
@endif
</div>
@endif
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_GROUPING_SELECTOR_AFTER, scopes: static::class) }}
</div>
@if ($isGlobalSearchVisible || $hasFiltersTrigger || $hasColumnManager)
<div>
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_SEARCH_BEFORE, scopes: static::class) }}
@if ($isGlobalSearchVisible)
@php
$searchPlaceholder = $getSearchPlaceholder();
@endphp
<x-filament-tables::search-field :debounce="$searchDebounce" :on-blur="$isSearchOnBlur"
:placeholder="$searchPlaceholder" />
@endif
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_SEARCH_AFTER, scopes: static::class) }}
@if ($hasFiltersTrigger || $hasColumnManager)
@if ($hasFiltersDialog)
@if ($filtersLayout === FiltersLayout::Modal || $filtersTriggerAction->isModalSlideOver())
@php
$filtersTriggerActionModalAlignment = $filtersTriggerAction->getModalAlignment();
$filtersTriggerActionIsModalAutofocused = $filtersTriggerAction->isModalAutofocused();
$filtersTriggerActionHasModalCloseButton = $filtersTriggerAction->hasModalCloseButton();
$filtersTriggerActionIsModalClosedByClickingAway = $filtersTriggerAction->isModalClosedByClickingAway();
$filtersTriggerActionIsModalClosedByEscaping = $filtersTriggerAction->isModalClosedByEscaping();
$filtersTriggerActionModalDescription = $filtersTriggerAction->getModalDescription();
$filtersTriggerActionExtraModalWindowAttributeBag = $filtersTriggerAction->getExtraModalWindowAttributeBag();
$filtersTriggerActionExtraModalOverlayAttributeBag = $filtersTriggerAction->getExtraModalOverlayAttributeBag();
$filtersTriggerActionVisibleModalFooterActions = $filtersTriggerAction->getVisibleModalFooterActions();
$filtersTriggerActionModalFooterActionsAlignment = $filtersTriggerAction->getModalFooterActionsAlignment();
$filtersTriggerActionModalHeading =
$filtersTriggerAction->getCustomModalHeading() ??
__('filament-tables::table.filters.heading');
$filtersTriggerActionModalIcon = $filtersTriggerAction->getModalIcon();
$filtersTriggerActionModalIconColor = $filtersTriggerAction->getModalIconColor();
$filtersTriggerActionIsModalSlideOver = $filtersTriggerAction->isModalSlideOver();
$filtersTriggerActionModalSlideOverPosition = $filtersTriggerAction->getModalSlideOverPosition();
$filtersTriggerActionIsModalFooterSticky = $filtersTriggerAction->isModalFooterSticky();
$filtersTriggerActionIsModalHeaderSticky = $filtersTriggerAction->isModalHeaderSticky();
@endphp
<x-filament::modal :alignment="$filtersTriggerActionModalAlignment" :autofocus="$filtersTriggerActionIsModalAutofocused" :close-button="$filtersTriggerActionHasModalCloseButton"
:close-by-clicking-away="$filtersTriggerActionIsModalClosedByClickingAway" :close-by-escaping="$filtersTriggerActionIsModalClosedByEscaping" :description="$filtersTriggerActionModalDescription" :extra-modal-window-attribute-bag="$filtersTriggerActionExtraModalWindowAttributeBag"
:extra-modal-overlay-attribute-bag="$filtersTriggerActionExtraModalOverlayAttributeBag" :footer-actions="$filtersTriggerActionVisibleModalFooterActions" :footer-actions-alignment="$filtersTriggerActionModalFooterActionsAlignment" :heading="$filtersTriggerActionModalHeading"
:icon="$filtersTriggerActionModalIcon" :icon-color="$filtersTriggerActionModalIconColor" :slide-over="$filtersTriggerActionIsModalSlideOver" :slide-over-position="$filtersTriggerActionModalSlideOverPosition"
:sticky-footer="$filtersTriggerActionIsModalFooterSticky" :sticky-header="$filtersTriggerActionIsModalHeaderSticky" :width="$filtersFormWidth"
:wire:key="$this->getId() . '.table.filters'" class="fi-ta-filters-modal">
<x-slot name="trigger">
{{ $filtersTriggerAction->badge($activeFiltersCount) }}
</x-slot>
{{ $filtersTriggerAction->getModalContent() }}
{{ $filtersForm }}
{{ $filtersTriggerAction->getModalContentFooter() }}
</x-filament::modal>
@else
<x-filament::dropdown :max-height="$filtersFormMaxHeight" placement="bottom-end" shift
:flip="false" :width="$filtersFormWidth ?? Width::ExtraSmall"
:wire:key="$this->getId() . '.table.filters'"
class="fi-ta-filters-dropdown">
<x-slot name="trigger">
{{ $filtersTriggerAction->badge($activeFiltersCount) }}
</x-slot>
<x-filament-tables::filters :apply-action="$filtersApplyAction" :form="$filtersForm"
:heading-tag="$secondLevelHeadingTag" :reset-action-position="$filtersResetActionPosition" />
</x-filament::dropdown>
@endif
@elseif ($hasFiltersBeforeContent || $hasFiltersAfterContent)
<span x-ref="filtersTriggerActionContainer" x-on:click="toggleFiltersDropdown"
@class([
'fi-ta-filters-trigger-action-ctn',
'lg:fi-hidden' => !$hasCollapsibleFilters,
])>
{{ $filtersTriggerAction->badge($activeFiltersCount) }}
</span>
@endif
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_COLUMN_MANAGER_TRIGGER_BEFORE, scopes: static::class) }}
@if ($hasColumnManager)
@php
$columnManagerMaxHeight = $getColumnManagerMaxHeight();
$columnManagerWidth = $getColumnManagerWidth();
$columnManagerColumns = $getColumnManagerColumns();
@endphp
@if ($columnManagerLayout === ColumnManagerLayout::Modal || $columnManagerTriggerAction->isModalSlideOver())
@php
$columnManagerTriggerActionModalAlignment = $columnManagerTriggerAction->getModalAlignment();
$columnManagerTriggerActionIsModalAutofocused = $columnManagerTriggerAction->isModalAutofocused();
$columnManagerTriggerActionHasModalCloseButton = $columnManagerTriggerAction->hasModalCloseButton();
$columnManagerTriggerActionIsModalClosedByClickingAway = $columnManagerTriggerAction->isModalClosedByClickingAway();
$columnManagerTriggerActionIsModalClosedByEscaping = $columnManagerTriggerAction->isModalClosedByEscaping();
$columnManagerTriggerActionModalDescription = $columnManagerTriggerAction->getModalDescription();
$columnManagerTriggerActionExtraModalWindowAttributeBag = $columnManagerTriggerAction->getExtraModalWindowAttributeBag();
$columnManagerTriggerActionExtraModalOverlayAttributeBag = $columnManagerTriggerAction->getExtraModalOverlayAttributeBag();
$columnManagerTriggerActionVisibleModalFooterActions = $columnManagerTriggerAction->getVisibleModalFooterActions();
$columnManagerTriggerActionModalFooterActionsAlignment = $columnManagerTriggerAction->getModalFooterActionsAlignment();
$columnManagerTriggerActionModalHeading =
$columnManagerTriggerAction->getCustomModalHeading() ??
__('filament-tables::table.column_manager.heading');
$columnManagerTriggerActionModalIcon = $columnManagerTriggerAction->getModalIcon();
$columnManagerTriggerActionModalIconColor = $columnManagerTriggerAction->getModalIconColor();
$columnManagerTriggerActionIsModalSlideOver = $columnManagerTriggerAction->isModalSlideOver();
$columnManagerTriggerActionModalSlideOverPosition = $columnManagerTriggerAction->getModalSlideOverPosition();
$columnManagerTriggerActionIsModalFooterSticky = $columnManagerTriggerAction->isModalFooterSticky();
$columnManagerTriggerActionIsModalHeaderSticky = $columnManagerTriggerAction->isModalHeaderSticky();
@endphp
<x-filament::modal :alignment="$columnManagerTriggerActionModalAlignment" :autofocus="$columnManagerTriggerActionIsModalAutofocused" :close-button="$columnManagerTriggerActionHasModalCloseButton"
:close-by-clicking-away="$columnManagerTriggerActionIsModalClosedByClickingAway" :close-by-escaping="$columnManagerTriggerActionIsModalClosedByEscaping" :description="$columnManagerTriggerActionModalDescription" :extra-modal-window-attribute-bag="$columnManagerTriggerActionExtraModalWindowAttributeBag"
:extra-modal-overlay-attribute-bag="$columnManagerTriggerActionExtraModalOverlayAttributeBag" :footer-actions="$columnManagerTriggerActionVisibleModalFooterActions" :footer-actions-alignment="$columnManagerTriggerActionModalFooterActionsAlignment" :heading="$columnManagerTriggerActionModalHeading"
:icon="$columnManagerTriggerActionModalIcon" :icon-color="$columnManagerTriggerActionModalIconColor" :slide-over="$columnManagerTriggerActionIsModalSlideOver" :slide-over-position="$columnManagerTriggerActionModalSlideOverPosition"
:sticky-footer="$columnManagerTriggerActionIsModalFooterSticky" :sticky-header="$columnManagerTriggerActionIsModalHeaderSticky" :width="$columnManagerWidth"
:wire:key="$this->getId() . '.table.column-manager'"
class="fi-ta-col-manager-modal">
<x-slot name="trigger">
{{ $columnManagerTriggerAction }}
</x-slot>
{{ $columnManagerTriggerAction->getModalContent() }}
<div x-data="filamentTableColumnManager({
columns: $wire.entangle('tableColumns'),
isLive: {{ $columnManagerApplyAction->isVisible() ? 'false' : 'true' }},
})"
x-on:apply-table-column-manager.window="applyTableColumnManager()"
x-on:reset-table-column-manager.window="resetDeferredColumns()"
class="fi-ta-col-manager">
<x-filament-tables::column-manager.content :columns="$columnManagerColumns"
:has-reorderable-columns="$hasReorderableColumns" :has-toggleable-columns="$hasToggleableColumns" :reorder-animation-duration="$getReorderAnimationDuration()" />
</div>
{{ $columnManagerTriggerAction->getModalContentFooter() }}
</x-filament::modal>
@else
<x-filament::dropdown :max-height="$columnManagerMaxHeight" placement="bottom-end" shift
:flip="false" :width="$columnManagerWidth"
:wire:key="$this->getId() . '.table.column-manager'"
class="fi-ta-col-manager-dropdown">
<x-slot name="trigger">
{{ $columnManagerTriggerAction }}
</x-slot>
<x-filament-tables::column-manager :apply-action="$columnManagerApplyAction" :columns="$columnManagerColumns"
:reset-action-position="$columnManagerResetActionPosition" :has-reorderable-columns="$hasReorderableColumns" :has-toggleable-columns="$hasToggleableColumns"
:heading-tag="$secondLevelHeadingTag" :reorder-animation-duration="$getReorderAnimationDuration()" />
</x-filament::dropdown>
@endif
@endif
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_COLUMN_MANAGER_TRIGGER_AFTER, scopes: static::class) }}
@endif
</div>
@endif
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_END) }}
</div>
{{ FilamentView::renderHook(TablesRenderHook::TOOLBAR_AFTER) }}
</div>
@if ($isReordering)
<div x-cloak wire:key="{{ $this->getId() }}.table.reorder.indicator" class="fi-ta-reorder-indicator">
{{ \Filament\Support\generate_loading_indicator_html(
new \Illuminate\View\ComponentAttributeBag([
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
'wire:target' => 'reorderTable',
]),
) }}
{{ __('filament-tables::table.reorder_indicator') }}
</div>
@elseif ($isSelectionEnabled && $maxSelectableRecords !== 1 && $isLoaded)
<div x-cloak x-bind:hidden="!getSelectedRecordsCount()" x-show="getSelectedRecordsCount()"
wire:key="{{ $this->getId() }}.table.selection.indicator" class="fi-ta-selection-indicator">
<div>
{{ \Filament\Support\generate_loading_indicator_html(
new \Illuminate\View\ComponentAttributeBag([
'x-show' => 'isLoading',
]),
) }}
<span
x-text="
window.pluralize(@js(__('filament-tables::table.selection_indicator.selected_count')), getSelectedRecordsCount(), {
count: new Intl.NumberFormat(@js(str_replace('_', '-', app()->getLocale()))).format(getSelectedRecordsCount()),
})
"></span>
</div>
@if (!$isSelectionDisabled)
<div>
{{ FilamentView::renderHook(TablesRenderHook::SELECTION_INDICATOR_ACTIONS_BEFORE, scopes: static::class) }}
<div class="fi-ta-selection-indicator-actions-ctn">
@if (!$selectsGroupsOnly)
<x-filament::link color="primary" tag="button" x-on:click="selectAllRecords"
x-show="canSelectAllRecords()" {{-- Make sure the Alpine attributes get re-evaluated after a Livewire request: --}}
:wire:key="$this->getId() . 'table.selection.indicator.actions.select-all.' . $allSelectableRecordsCount . '.' . $page">
{{ trans_choice('filament-tables::table.selection_indicator.actions.select_all.label', $allSelectableRecordsCount, ['count' => \Illuminate\Support\Number::format($allSelectableRecordsCount, locale: app()->getLocale())]) }}
</x-filament::link>
@endif
<x-filament::link color="danger" tag="button" x-on:click="deselectAllRecords">
{{ __('filament-tables::table.selection_indicator.actions.deselect_all.label') }}
</x-filament::link>
</div>
{{ FilamentView::renderHook(TablesRenderHook::SELECTION_INDICATOR_ACTIONS_AFTER, scopes: static::class) }}
</div>
@endif
</div>
@endif
@if ($filterIndicators)
@if (filled(
$filterIndicatorsView = FilamentView::renderHook(TablesRenderHook::FILTER_INDICATORS,
scopes: static::class,
data: ['filterIndicators' => $filterIndicators])))
{{ $filterIndicatorsView }}
@else
<div class="fi-ta-filter-indicators">
<div>
<span class="fi-ta-filter-indicators-label">
{{ __('filament-tables::table.filters.indicator') }}
</span>
<div class="fi-ta-filter-indicators-badges-ctn">
@foreach ($filterIndicators as $indicator)
@php
$indicatorColor = $indicator->getColor();
@endphp
<x-filament::badge :color="$indicatorColor">
{{ $indicator->getLabel() }}
@if ($indicator->isRemovable())
@php
$indicatorRemoveLivewireClickHandler = $indicator->getRemoveLivewireClickHandler();
@endphp
<x-slot name="deleteButton" :label="__('filament-tables::table.filters.actions.remove.label')"
:wire:click="$indicatorRemoveLivewireClickHandler"
wire:loading.attr="disabled" wire:target="removeTableFilter"></x-slot>
@endif
</x-filament::badge>
@endforeach
</div>
</div>
@if (collect($filterIndicators)->contains(fn(\Filament\Tables\Filters\Indicator $indicator): bool => $indicator->isRemovable()))
{{ $getFiltersRemoveAllAction() }}
@endif
</div>
@endif
@endif
@if ((!$content && !$hasColumnsLayout) || $records === null || count($records))
<div @if (!$isReordering && ($pollingInterval = $getPollingInterval())) wire:poll.{{ $pollingInterval }} @endif
class="fi-ta-content-ctn fi-fixed-positioning-context">
@if ($hasContentLayout && $records !== null && count($records))
@if (!$isReordering)
@php
$sortableColumns = array_filter(
$columns,
fn(\Filament\Tables\Columns\Column $column): bool => $column->isSortable(),
);
@endphp
@if (
($isSelectionEnabled && $maxSelectableRecords !== 1 && !$isReordering && !$selectsGroupsOnly) ||
count($sortableColumns))
<div class="fi-ta-content-header">
@if ($isSelectionEnabled && $maxSelectableRecords !== 1 && !$isReordering && !$selectsGroupsOnly)
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_page.label') }}"
type="checkbox"
@if ($isSelectionDisabled) disabled
@elseif ($maxSelectableRecords)
x-bind:disabled="
const recordsOnPage = getRecordsOnPage()
return recordsOnPage.length && ! areRecordsToggleable(recordsOnPage)
" @endif
x-bind:checked="const recordsOnPage = getRecordsOnPage()
if (recordsOnPage.length && areRecordsSelected(recordsOnPage)) {
$el.checked = true
return 'checked'
}
$el.checked = false
return null"
x-on:click="toggleSelectRecordsOnPage" {{-- Make sure the "checked" state gets re-evaluated after a Livewire request: --}}
wire:key="{{ $this->getId() }}.table.bulk-select-page.checkbox.{{ \Illuminate\Support\Str::random() }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-page-checkbox fi-checkbox-input" />
@endif
@if (count($sortableColumns))
<div x-data="{
sort: $wire.$entangle('tableSort', true),
column: null,
direction: null,
}" x-init="if (sort) {
;
[column, direction] = sort.split(':')
direction ??= 'asc'
}
$watch('sort', function() {
if (!sort) {
return
}
;
[column, direction] = sort.split(':')
direction ??= 'asc'
})
$watch('direction', function() {
sort = column ? `${column}:${direction}` : null
})
$watch('column', function(newColumn, oldColumn) {
if (!newColumn) {
direction = null
sort = column ? `${column}:${direction}` : null
return
}
if (oldColumn) {
sort = column ? `${column}:${direction}` : null
return
}
direction = 'asc'
sort = column ? `${column}:${direction}` : null
})"
class="fi-ta-sorting-settings">
<label>
<x-filament::input.wrapper :prefix="__('filament-tables::table.sorting.fields.column.label')">
<x-filament::input.select x-model="column">
<option value="">
{{ $defaultSortOptionLabel }}
</option>
@foreach ($sortableColumns as $column)
<option value="{{ $column->getName() }}">
{{ $column->getLabel() }}
</option>
@endforeach
</x-filament::input.select>
</x-filament::input.wrapper>
</label>
<label x-cloak x-show="column">
<span class="fi-sr-only">
{{ __('filament-tables::table.sorting.fields.direction.label') }}
</span>
<x-filament::input.wrapper>
<x-filament::input.select x-model="direction">
<option value="asc">
{{ __('filament-tables::table.sorting.fields.direction.options.asc') }}
</option>
<option value="desc">
{{ __('filament-tables::table.sorting.fields.direction.options.desc') }}
</option>
</x-filament::input.select>
</x-filament::input.wrapper>
</label>
</div>
@endif
</div>
@endif
@endif
@if ($content)
{{ $content->with(['records' => $records]) }}
@else
<div @if ($isReorderable) x-on:end.stop="
$wire.reorderTable(
$event.target.sortable.toArray(),
$event.item.getAttribute('x-sortable-item'),
)
"
x-sortable
data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}" @endif
{{ new ComponentAttributeBag()->when($contentGrid, fn(ComponentAttributeBag $attributes) => $attributes->grid($contentGrid))->class([
'fi-ta-content',
'fi-ta-content-grid' => $contentGrid,
'fi-ta-content-grouped' => $this->getTableGrouping(),
]) }}>
@php
$previousRecord = null;
$previousRecordGroupKey = null;
$previousRecordGroupTitle = null;
@endphp
@foreach ($records as $record)
@php
$recordAction = $getRecordAction($record);
$recordKey = $getRecordKey($record);
$recordUrl = $getRecordUrl($record);
$openRecordUrlInNewTab = $shouldOpenRecordUrlInNewTab($record);
$recordGroupKey = $group?->getStringKey($record);
$recordGroupTitle = $group?->getTitle($record);
$isRecordGroupCollapsible = $group?->isCollapsible();
$collapsibleColumnsLayout?->record($record)->recordKey($recordKey);
$hasCollapsibleColumnsLayout = (bool) $collapsibleColumnsLayout?->isVisible();
$recordActions = array_reduce(
$defaultRecordActions,
function (array $carry, $action) use ($record): array {
$action = $action->getClone();
if (!$action instanceof \Filament\Actions\BulkAction) {
$action->record($record);
}
if ($action->isHidden()) {
return $carry;
}
$carry[] = $action;
return $carry;
},
initial: [],
);
@endphp
@if ((string) $recordGroupTitle !== (string) $previousRecordGroupTitle)
@if ($hasSummary && !$isReordering && filled($previousRecordGroupTitle))
<table @class(['fi-ta-table', 'fi-ta-table-reordering' => $isReordering])>
<tbody>
@php
$groupScopedAllTableSummaryQuery = $group->scopeQuery(
$this->getAllTableSummaryQuery(),
$previousRecord,
);
@endphp
<x-filament-tables::summary.row :columns="$columns"
extra-heading-column :heading="__(
'filament-tables::table.summary.subheadings.group',
[
'group' => $previousRecordGroupTitle,
'label' => $pluralModelLabel,
],
)" :placeholder-columns="false"
:query="$groupScopedAllTableSummaryQuery" :selected-state="$groupedSummarySelectedState[
$previousRecordGroupKey
] ?? []" />
</tbody>
</table>
@endif
<div @if ($isRecordGroupCollapsible = $group->isCollapsible()) x-on:click="toggleCollapseGroup(@js($recordGroupTitle))"
@if (!$hasSummary)
x-bind:class="{ 'fi-collapsed': isGroupCollapsed(@js($recordGroupTitle)) }" @endif
@endif
@class([
'fi-ta-group-header',
'fi-collapsible' => $isRecordGroupCollapsible,
])
>
@if ($isSelectionEnabled && $maxSelectableRecords !== 1)
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_group.label', ['title' => $recordGroupTitle]) }}"
type="checkbox"
data-group-selectable-record-keys="{{ json_encode($this->getGroupedSelectableTableRecordKeys($recordGroupKey)) }}"
@if ($isSelectionDisabled) disabled
@else
x-on:click="toggleSelectRecords(JSON.parse($el.dataset.groupSelectableRecordKeys))"
@if ($maxSelectableRecords)
x-bind:disabled="
const recordsInGroup = JSON.parse($el.dataset.groupSelectableRecordKeys)
return recordsInGroup.length && ! areRecordsToggleable(recordsInGroup)
" @endif
@endif
x-bind:checked="const recordsInGroup = JSON.parse($el.dataset.groupSelectableRecordKeys)
if (recordsInGroup.length && areRecordsSelected(recordsInGroup)) {
$el.checked = true
return 'checked'
}
$el.checked = false
return null"
wire:key="{{ $this->getId() }}.table.bulk_select_group.checkbox.{{ $page }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-group-checkbox fi-checkbox-input"
/>
@endif
<div>
<{{ $secondLevelHeadingTag }} class="fi-ta-group-heading">
@if (filled($recordGroupLabel = $group->isTitlePrefixedWithLabel() ? $group->getLabel() : null))
{{ $recordGroupLabel }}:
@endif
{{ $recordGroupTitle }}
</{{ $secondLevelHeadingTag }}>
@if (filled($recordGroupDescription = $group->getDescription($record, $recordGroupTitle)))
<p class="fi-ta-group-description">
{{ $recordGroupDescription }}
</p>
@endif
</div>
@if ($isRecordGroupCollapsible)
<button
aria-label="{{ filled($recordGroupLabel) ? $recordGroupLabel . ': ' . $recordGroupTitle : $recordGroupTitle }}"
x-bind:aria-expanded="!isGroupCollapsed(@js($recordGroupTitle))"
type="button" class="fi-icon-btn fi-size-sm">
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::ChevronUp, alias: \Filament\Tables\View\TablesIconAlias::GROUPING_COLLAPSE_BUTTON, size: \Filament\Support\Enums\IconSize::Small) }}
</button>
@endif
</div>
@endif
<div @if ($hasCollapsibleColumnsLayout) x-data="{ isCollapsed: @js($collapsibleColumnsLayout->isCollapsed()) }"
x-init="$dispatch('collapsible-table-row-initialized')"
x-on:collapse-all-table-rows.window="isCollapsed = true"
x-on:expand-all-table-rows.window="isCollapsed = false"
x-bind:class="isCollapsed && 'fi-ta-record-collapsed'" @endif
wire:key="{{ $this->getId() }}.table.records.{{ $recordKey }}"
@if ($isReordering) x-sortable-item="{{ $recordKey }}"
x-sortable-handle @endif
@class([
'fi-ta-record',
'fi-clickable' => $recordUrl || $recordAction,
'fi-ta-record-with-content-prefix' =>
$isReordering || ($isSelectionEnabled && $isRecordSelectable($record)),
'fi-ta-record-with-content-suffix' =>
$hasCollapsibleColumnsLayout && !$isReordering,
...$getRecordClasses($record),
])
x-bind:class="{
{{ $group?->isCollapsible() ? '\'fi-collapsed\': isGroupCollapsed(' . \Illuminate\Support\Js::from($recordGroupTitle) . '),' : '' }}
'fi-selected': isRecordSelected(@js($recordKey)),
}">
@php
$hasItemBeforeRecordContent =
$isReordering || ($isSelectionEnabled && $isRecordSelectable($record));
$hasItemAfterRecordContent = $hasCollapsibleColumnsLayout && !$isReordering;
@endphp
@if ($isReordering)
<button class="fi-ta-reorder-handle fi-icon-btn" type="button">
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::Bars2, alias: \Filament\Tables\View\TablesIconAlias::REORDER_HANDLE) }}
</button>
@elseif ($isSelectionEnabled && $isRecordSelectable($record))
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_record.label', ['key' => $recordKey]) }}"
type="checkbox"
@if ($isSelectionDisabled) disabled
@elseif ($maxSelectableRecords && $maxSelectableRecords !== 1)
x-bind:disabled="! areRecordsToggleable([@js($recordKey)])" @endif
value="{{ $recordKey }}"
x-on:click="toggleSelectedRecord(@js($recordKey))"
x-bind:checked="isRecordSelected(@js($recordKey)) ? 'checked' : null"
data-group="{{ $recordGroupKey }}" wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-record-checkbox fi-checkbox-input" />
@endif
<div class="fi-ta-record-content-ctn">
<div>
@if ($recordUrl)
<a {{ \Filament\Support\generate_href_html($recordUrl, $openRecordUrlInNewTab, hasNestedClickEventHandler: true) }}
{{ $getExtraRecordLinkAttributeBag($record)->class(['fi-ta-record-content']) }}>
@foreach ($columnsLayout as $columnsLayoutComponent)
{{ $columnsLayoutComponent->record($record)->recordKey($recordKey)->rowLoop($loop)->renderInLayout() }}
@endforeach
</a>
@elseif ($recordAction)
@php
$recordWireClickAction = $getRecordAction($record)
? "mountTableAction('{$recordAction}', '{$recordKey}')"
: ($recordWireClickAction = "{$recordAction}('{$recordKey}')");
@endphp
<button type="button" wire:click="{{ $recordWireClickAction }}"
wire:loading.attr="disabled"
wire:target="{{ $recordWireClickAction }}"
class="fi-ta-record-content">
@foreach ($columnsLayout as $columnsLayoutComponent)
{{ $columnsLayoutComponent->record($record)->recordKey($recordKey)->rowLoop($loop)->renderInLayout() }}
@endforeach
</button>
@else
<div class="fi-ta-record-content">
@foreach ($columnsLayout as $columnsLayoutComponent)
{{ $columnsLayoutComponent->record($record)->recordKey($recordKey)->rowLoop($loop)->renderInLayout() }}
@endforeach
</div>
@endif
@if ($hasCollapsibleColumnsLayout && !$isReordering)
<div x-collapse x-show="! isCollapsed"
class="fi-ta-record-content fi-collapsible">
{{ $collapsibleColumnsLayout }}
</div>
@endif
</div>
@if ($recordActions && !$isReordering)
<div @class([
'fi-ta-actions fi-wrapped sm:fi-not-wrapped',
match ($recordActionsAlignment ?? Alignment::Start) {
Alignment::Start => 'fi-align-start',
Alignment::Center => 'fi-align-center',
Alignment::End => 'fi-align-end',
} => $contentGrid,
'fi-align-start md:fi-align-end' => !$contentGrid,
'fi-ta-actions-before-columns-position' =>
$recordActionsPosition === RecordActionsPosition::BeforeColumns,
])>
@foreach ($recordActions as $action)
{{ $action }}
@endforeach
</div>
@endif
</div>
@if ($hasCollapsibleColumnsLayout && !$isReordering)
<button type="button" x-on:click="isCollapsed = ! isCollapsed"
class="fi-ta-record-collapse-btn fi-icon-btn">
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::ChevronDown, alias: \Filament\Tables\View\TablesIconAlias::COLUMNS_COLLAPSE_BUTTON) }}
</button>
@endif
</div>
@php
$previousRecordGroupKey = $recordGroupKey;
$previousRecordGroupTitle = $recordGroupTitle;
$previousRecord = $record;
@endphp
@endforeach
@if (
$hasSummary &&
!$isReordering &&
filled($previousRecordGroupTitle) &&
$this->shouldRenderTrailingGroupedTableSummary($previousRecord))
<table class="fi-ta-table">
<tbody>
@php
$groupScopedAllTableSummaryQuery = $group->scopeQuery(
$this->getAllTableSummaryQuery(),
$previousRecord,
);
@endphp
<x-filament-tables::summary.row :columns="$columns" extra-heading-column
:heading="__('filament-tables::table.summary.subheadings.group', [
'group' => $previousRecordGroupTitle,
'label' => $pluralModelLabel,
])" :placeholder-columns="false" :query="$groupScopedAllTableSummaryQuery"
:selected-state="$groupedSummarySelectedState[$previousRecordGroupKey] ?? []" />
</tbody>
</table>
@endif
</div>
@endif
@if (($content || $hasColumnsLayout) && $contentFooter)
{{ $contentFooter->with([
'columns' => $columns,
'records' => $records,
]) }}
@endif
@if ($hasTopLevelSummary && !$isReordering)
<table class="fi-ta-table">
<tbody>
<x-filament-tables::summary :all-table-summary="$hasAllTableSummary" :columns="$columns"
extra-heading-column :page-summary="$hasPageSummary" :placeholder-columns="false" :plural-model-label="$pluralModelLabel"
:records="$records" />
</tbody>
</table>
@endif
@elseif (!($content || $hasColumnsLayout) && $records !== null)
@php
$sortableColumns = $isStackedOnMobile
? array_filter(
$columns,
fn(\Filament\Tables\Columns\Column $column): bool => $column->isSortable(),
)
: [];
@endphp
<table @class([
'fi-ta-table',
'fi-ta-table-stacked-on-mobile' => $isStackedOnMobile,
])>
<thead>
@if (
$isStackedOnMobile &&
(count($sortableColumns) || ($isSelectionEnabled && $maxSelectableRecords !== 1 && !$selectsGroupsOnly)) &&
!$isReordering)
<tr class="fi-ta-table-stacked-header-row">
<th colspan="100%" class="fi-ta-table-stacked-header-cell">
@if (count($sortableColumns))
<div x-data="{
sort: $wire.$entangle('tableSort', true),
column: null,
direction: null,
}" x-init="if (sort) {
;
[column, direction] = sort.split(':')
direction ??= 'asc'
}
$watch('sort', function() {
if (!sort) {
return
}
;
[column, direction] = sort.split(':')
direction ??= 'asc'
})
$watch('direction', function() {
sort = column ? `${column}:${direction}` : null
})
$watch('column', function(newColumn, oldColumn) {
if (!newColumn) {
direction = null
sort = column ? `${column}:${direction}` : null
return
}
if (oldColumn) {
sort = column ? `${column}:${direction}` : null
return
}
direction = 'asc'
sort = column ? `${column}:${direction}` : null
})"
class="fi-ta-table-stacked-sorting">
<label>
<x-filament::input.wrapper :prefix="__(
'filament-tables::table.sorting.fields.column.label',
)">
<x-filament::input.select x-model="column">
<option value="">
{{ $defaultSortOptionLabel }}
</option>
@foreach ($sortableColumns as $sortableColumn)
<option value="{{ $sortableColumn->getName() }}">
{{ $sortableColumn->getLabel() }}
</option>
@endforeach
</x-filament::input.select>
</x-filament::input.wrapper>
</label>
<label x-cloak x-show="column">
<span class="fi-sr-only">
{{ __('filament-tables::table.sorting.fields.direction.label') }}
</span>
<x-filament::input.wrapper>
<x-filament::input.select x-model="direction">
<option value="asc">
{{ __('filament-tables::table.sorting.fields.direction.options.asc') }}
</option>
<option value="desc">
{{ __('filament-tables::table.sorting.fields.direction.options.desc') }}
</option>
</x-filament::input.select>
</x-filament::input.wrapper>
</label>
</div>
@endif
@if ($isSelectionEnabled && $maxSelectableRecords !== 1 && !$selectsGroupsOnly)
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_page.label') }}"
type="checkbox"
@if ($isSelectionDisabled) disabled
@elseif ($maxSelectableRecords)
x-bind:disabled="
const recordsOnPage = getRecordsOnPage()
return recordsOnPage.length && ! areRecordsToggleable(recordsOnPage)
" @endif
x-bind:checked="const recordsOnPage = getRecordsOnPage()
if (recordsOnPage.length && areRecordsSelected(recordsOnPage)) {
$el.checked = true
return 'checked'
}
$el.checked = false
return null"
x-on:click="toggleSelectRecordsOnPage" {{-- Make sure the "checked" state gets re-evaluated after a Livewire request: --}}
wire:key="{{ $this->getId() }}.table.bulk-select-page.checkbox.stacked.{{ \Illuminate\Support\Str::random() }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-page-checkbox fi-checkbox-input" />
@endif
</th>
</tr>
@endif
@if ($hasColumnGroups)
<tr class="fi-ta-table-head-groups-row">
@if (count($records))
@if ($isReordering)
<th></th>
@else
@if (count($defaultRecordActions) &&
in_array($recordActionsPosition, [RecordActionsPosition::BeforeCells, RecordActionsPosition::BeforeColumns]))
<th></th>
@endif
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::BeforeCells)
<th></th>
@endif
@endif
@endif
@foreach ($columnsLayout as $columnGroup)
@if ($columnGroup instanceof Column)
@if ($columnGroup->isVisible() && !$columnGroup->isToggledHidden())
<th></th>
@endif
@elseif ($columnGroup instanceof ColumnGroup)
@php
$columnGroupColumnsCount = count($columnGroup->getVisibleColumns());
@endphp
@if ($columnGroupColumnsCount)
<th colspan="{{ $columnGroupColumnsCount }}"
{{ $columnGroup->getExtraHeaderAttributeBag()->class([
'fi-ta-header-group-cell',
'fi-wrapped' => $columnGroup->canHeaderWrap(),
($columnGroupAlignment = $columnGroup->getAlignment()) instanceof \Filament\Support\Enums\Alignment
? "fi-align-{$columnGroupAlignment->value}"
: (is_string($columnGroupAlignment)
? $columnGroupAlignment
: ''),
filled($columnGroupHiddenFrom = $columnGroup->getHiddenFrom()) ? "{$columnGroupHiddenFrom}:fi-hidden" : '',
filled($columnGroupVisibleFrom = $columnGroup->getVisibleFrom()) ? "{$columnGroupVisibleFrom}:fi-visible" : '',
]) }}>
{{ $columnGroup->getLabel() }}
</th>
@endif
@endif
@endforeach
@if (!$isReordering && count($records))
@if (count($defaultRecordActions) &&
in_array($recordActionsPosition, [RecordActionsPosition::AfterColumns, RecordActionsPosition::AfterCells]))
<th></th>
@endif
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::AfterCells)
<th></th>
@endif
@endif
</tr>
@endif
<tr>
@if (count($records))
@if ($isReordering)
<th></th>
@else
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::BeforeCells)
@if ($recordActionsColumnLabel)
<th class="fi-ta-header-cell">
{{ $recordActionsColumnLabel }}
</th>
@else
<th aria-label="{{ trans_choice('filament-tables::table.columns.actions.label', $flatRecordActionsCount) }}"
class="fi-ta-actions-header-cell fi-ta-empty-header-cell"></th>
@endif
@endif
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::BeforeCells)
<th class="fi-ta-cell fi-ta-selection-cell">
@if ($maxSelectableRecords !== 1 && !$selectsGroupsOnly)
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_page.label') }}"
type="checkbox"
@if ($isSelectionDisabled) disabled
@elseif ($maxSelectableRecords)
x-bind:disabled="
const recordsOnPage = getRecordsOnPage()
return recordsOnPage.length && ! areRecordsToggleable(recordsOnPage)
" @endif
x-bind:checked="const recordsOnPage = getRecordsOnPage()
if (recordsOnPage.length && areRecordsSelected(
recordsOnPage)) {
$el.checked = true
return 'checked'
}
$el.checked = false
return null"
x-on:click="toggleSelectRecordsOnPage"
{{-- Make sure the "checked" state gets re-evaluated after a Livewire request: --}}
wire:key="{{ $this->getId() }}.table.bulk-select-page.checkbox.{{ \Illuminate\Support\Str::random() }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-page-checkbox fi-checkbox-input" />
@endif
</th>
@endif
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::BeforeColumns)
@if ($recordActionsColumnLabel)
<th class="fi-ta-header-cell">
{{ $recordActionsColumnLabel }}
</th>
@else
<th aria-label="{{ trans_choice('filament-tables::table.columns.actions.label', $flatRecordActionsCount) }}"
class="fi-ta-actions-header-cell fi-ta-empty-header-cell"></th>
@endif
@endif
@endif
@endif
@php
$hasHeaderCellRenderHook = FilamentView::hasRenderHook(
TablesRenderHook::HEADER_CELL,
scopes: static::class,
);
@endphp
@foreach ($columns as $column)
@if (
$hasHeaderCellRenderHook &&
filled(
$headerCellView = FilamentView::renderHook(TablesRenderHook::HEADER_CELL,
scopes: static::class,
data: [
'column' => $column,
'isReordering' => $isReordering,
])))
{{ $headerCellView }}
@else
@php
$columnName = $column->getName();
$columnLabel = $column->getLabel();
$columnAlignment = $column->getAlignment();
$columnWidth = $column->getWidth();
$isColumnActivelySorted = $getSortColumn() === $column->getName();
$isColumnSortable = $column->isSortable() && !$isReordering;
$columnHeaderTooltip = $column->getHeaderTooltip();
$columnHeaderTooltipAttribute =
$columnHeaderTooltip instanceof
\Illuminate\Contracts\Support\Htmlable
? 'x-tooltip.html'
: 'x-tooltip';
@endphp
<th @if ($isColumnActivelySorted) aria-sort="{{ $sortDirection === 'asc' ? 'ascending' : 'descending' }}" @endif
{{ $column->getExtraHeaderAttributeBag()->class([
'fi-ta-header-cell',
'fi-ta-header-cell-' . str($columnName)->camel()->kebab(),
'fi-growable' => blank($columnWidth) && $column->canGrow(default: false),
'fi-grouped' => $column->getGroup(),
'fi-wrapped' => $column->canHeaderWrap(),
'fi-ta-header-cell-sorted' => $isColumnActivelySorted,
($columnAlignment = $column->getAlignment()) instanceof \Filament\Support\Enums\Alignment
? "fi-align-{$columnAlignment->value}"
: (is_string($columnAlignment)
? $columnAlignment
: ''),
filled($columnHiddenFrom = $column->getHiddenFrom()) ? "{$columnHiddenFrom}:fi-hidden" : '',
filled($columnVisibleFrom = $column->getVisibleFrom()) ? "{$columnVisibleFrom}:fi-visible" : '',
])->style([
'width: ' . e($columnWidth) => filled($columnWidth),
]) }}>
@if ($isColumnSortable)
<span aria-label="{{ trim(strip_tags($columnLabel)) }}"
role="button" tabindex="0"
wire:click="sortTable('{{ $columnName }}')"
x-on:keydown.enter.prevent.stop="$wire.sortTable('{{ $columnName }}')"
x-on:keydown.space.prevent.stop="$wire.sortTable('{{ $columnName }}')"
wire:loading.attr="disabled"
class="fi-ta-header-cell-sort-btn">
@if (filled($columnHeaderTooltip))
<span
{{ $columnHeaderTooltipAttribute }}="{
content: @js($columnHeaderTooltip),
theme: $store.theme,
}"
class="fi-ta-header-cell-tooltip">
{{ $columnLabel }}
</span>
@else
{{ $columnLabel }}
@endif
{{ \Filament\Support\generate_icon_html(
$isColumnActivelySorted && $sortDirection === 'asc'
? \Filament\Support\Icons\Heroicon::ChevronUp
: \Filament\Support\Icons\Heroicon::ChevronDown,
alias: match (true) {
$isColumnActivelySorted && $sortDirection === 'asc'
=> \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_ASC_BUTTON,
$isColumnActivelySorted && $sortDirection === 'desc'
=> \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_DESC_BUTTON,
default => \Filament\Tables\View\TablesIconAlias::HEADER_CELL_SORT_BUTTON,
},
attributes: new \Illuminate\View\ComponentAttributeBag([
'wire:loading.remove.delay.' . config('filament.livewire_loading_delay', 'default') => true,
'wire:target' => "sortTable('{$columnName}')",
]),
) }}
{{ \Filament\Support\generate_loading_indicator_html(
new \Illuminate\View\ComponentAttributeBag([
'wire:loading.delay.' . config('filament.livewire_loading_delay', 'default') => '',
'wire:target' => "sortTable('{$columnName}')",
]),
) }}
</span>
@else
@if (filled($columnHeaderTooltip))
<span
{{ $columnHeaderTooltipAttribute }}="{
content: @js($columnHeaderTooltip),
theme: $store.theme,
}"
class="fi-ta-header-cell-tooltip">
{{ $columnLabel }}
</span>
@else
{{ $columnLabel }}
@endif
@endif
</th>
@endif
@endforeach
@if (!$isReordering && count($records))
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::AfterColumns)
@if ($recordActionsColumnLabel)
<th class="fi-ta-header-cell fi-align-end">
{{ $recordActionsColumnLabel }}
</th>
@else
<th aria-label="{{ trans_choice('filament-tables::table.columns.actions.label', $flatRecordActionsCount) }}"
class="fi-ta-actions-header-cell fi-ta-empty-header-cell"></th>
@endif
@endif
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::AfterCells)
<th class="fi-ta-cell fi-ta-selection-cell">
@if ($maxSelectableRecords !== 1 && !$selectsGroupsOnly)
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_page.label') }}"
type="checkbox"
@if ($isSelectionDisabled) disabled
@elseif ($maxSelectableRecords)
x-bind:disabled="
const recordsOnPage = getRecordsOnPage()
return recordsOnPage.length && ! areRecordsToggleable(recordsOnPage)
" @endif
x-bind:checked="const recordsOnPage = getRecordsOnPage()
if (recordsOnPage.length && areRecordsSelected(recordsOnPage)) {
$el.checked = true
return 'checked'
}
$el.checked = false
return null"
x-on:click="toggleSelectRecordsOnPage" {{-- Make sure the "checked" state gets re-evaluated after a Livewire request: --}}
wire:key="{{ $this->getId() }}.table.bulk-select-page.checkbox.{{ \Illuminate\Support\Str::random() }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-page-checkbox fi-checkbox-input" />
@endif
</th>
@endif
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::AfterCells)
@if ($recordActionsColumnLabel)
<th class="fi-ta-header-cell fi-align-end">
{{ $recordActionsColumnLabel }}
</th>
@else
<th aria-label="{{ trans_choice('filament-tables::table.columns.actions.label', $flatRecordActionsCount) }}"
class="fi-ta-actions-header-cell fi-ta-empty-header-cell"></th>
@endif
@endif
@endif
</tr>
</thead>
@if ($isColumnSearchVisible || count($records))
<tbody
@if ($isReorderable) x-on:end.stop="
$wire.reorderTable(
$event.target.sortable.toArray(),
$event.item.getAttribute('x-sortable-item'),
)
"
x-sortable
data-sortable-animation-duration="{{ $getReorderAnimationDuration() }}" @endif>
@if ($isColumnSearchVisible)
<tr class="fi-ta-row fi-ta-row-not-reorderable">
@if (count($records))
@if ($isReordering)
<td></td>
@else
@if (count($defaultRecordActions) &&
in_array($recordActionsPosition, [RecordActionsPosition::BeforeCells, RecordActionsPosition::BeforeColumns]))
<td></td>
@endif
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::BeforeCells)
<td></td>
@endif
@endif
@endif
@foreach ($columns as $column)
@php
$columnName = $column->getName();
@endphp
<td @class([
'fi-ta-cell',
'fi-ta-individual-search-cell' => ($isIndividuallySearchable = $column->isIndividuallySearchable()),
'fi-ta-individual-search-cell-' .
str($columnName)->camel()->kebab() => $isIndividuallySearchable,
])>
@if ($isIndividuallySearchable)
<x-filament-tables::search-field :debounce="$searchDebounce"
:on-blur="$isSearchOnBlur" :wire-model="'tableColumnSearches.' . $columnName" />
@endif
</td>
@endforeach
@if (!$isReordering && count($records))
@if (count($defaultRecordActions) &&
in_array($recordActionsPosition, [RecordActionsPosition::AfterColumns, RecordActionsPosition::AfterCells]))
<td></td>
@endif
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::AfterCells)
<td></td>
@endif
@endif
</tr>
@endif
@if (count($records))
@php
$isRecordRowStriped = false;
$previousRecord = null;
$previousRecordGroupKey = null;
$previousRecordGroupTitle = null;
@endphp
@foreach ($records as $record)
@php
$recordAction = $getRecordAction($record);
$recordKey = $getRecordKey($record);
$recordUrl = $getRecordUrl($record);
$openRecordUrlInNewTab = $shouldOpenRecordUrlInNewTab($record);
$recordGroupKey = $group?->getStringKey($record);
$recordGroupTitle = $group?->getTitle($record);
$recordActions = array_reduce(
$defaultRecordActions,
function (array $carry, $action) use ($record): array {
$action = $action->getClone();
if (!$action instanceof \Filament\Actions\BulkAction) {
$action->record($record);
}
if ($action->isHidden()) {
return $carry;
}
$carry[] = $action;
return $carry;
},
initial: [],
);
@endphp
@if ((string) $recordGroupTitle !== (string) $previousRecordGroupTitle)
@if ($hasSummary && !$isReordering && filled($previousRecordGroupTitle))
@php
$groupColumn = $group->getColumn();
$groupScopedAllTableSummaryQuery = $group->scopeQuery(
$this->getAllTableSummaryQuery(),
$previousRecord,
);
@endphp
<x-filament-tables::summary.row :actions="count($defaultRecordActions)"
:actions-position="$recordActionsPosition" :columns="$columns" :group-column="$groupColumn"
:groups-only="$isGroupsOnly" :heading="$isGroupsOnly
? $previousRecordGroupTitle
: __('filament-tables::table.summary.subheadings.group', [
'group' => $previousRecordGroupTitle,
'label' => $pluralModelLabel,
])" :query="$groupScopedAllTableSummaryQuery"
:record-checkbox-position="$recordCheckboxPosition" :selected-state="$groupedSummarySelectedState[
$previousRecordGroupKey
] ?? []" :selection-enabled="$isSelectionEnabled" />
@endif
@if (!$isGroupsOnly)
<tr class="fi-ta-row fi-ta-group-header-row">
@php
$isRecordGroupCollapsible = $group?->isCollapsible();
$groupHeaderColspan = $columnsCount;
if ($isSelectionEnabled) {
$groupHeaderColspan--;
if (
$recordCheckboxPosition ===
RecordCheckboxPosition::BeforeCells &&
count($defaultRecordActions) &&
$recordActionsPosition ===
RecordActionsPosition::BeforeCells
) {
$groupHeaderColspan--;
}
}
@endphp
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::BeforeCells)
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::BeforeCells)
<td></td>
@endif
<td class="fi-ta-cell fi-ta-group-selection-cell">
@if ($maxSelectableRecords !== 1)
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_group.label', ['title' => $recordGroupTitle]) }}"
type="checkbox"
data-group-selectable-record-keys="{{ json_encode($this->getGroupedSelectableTableRecordKeys($recordGroupKey)) }}"
@if ($isSelectionDisabled) disabled
@else
x-on:click="toggleSelectRecords(JSON.parse($el.dataset.groupSelectableRecordKeys))"
@if ($maxSelectableRecords)
x-bind:disabled="
const recordsInGroup = JSON.parse($el.dataset.groupSelectableRecordKeys)
return recordsInGroup.length && ! areRecordsToggleable(recordsInGroup)
" @endif
@endif
x-bind:checked="const recordsInGroup = JSON.parse($el.dataset
.groupSelectableRecordKeys)
if (recordsInGroup.length && areRecordsSelected(
recordsInGroup)) {
$el.checked = true
return 'checked'
}
$el.checked = false
return null"
wire:key="{{ $this->getId() }}.table.bulk_select_group.checkbox.{{ $page }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-group-checkbox fi-checkbox-input"
/>
@endif
</td>
@endif
<td colspan="{{ $groupHeaderColspan }}"
class="fi-ta-group-header-cell">
<div @if ($isRecordGroupCollapsible) x-on:click="toggleCollapseGroup(@js($recordGroupTitle))"
x-bind:class="isGroupCollapsed(@js($recordGroupTitle)) ? 'fi-collapsed' : null" @endif
@class([
'fi-ta-group-header',
'fi-collapsible' => $isRecordGroupCollapsible,
])>
<div>
<{{ $secondLevelHeadingTag }}
class="fi-ta-group-heading">
@if (filled($recordGroupLabel = $group->isTitlePrefixedWithLabel() ? $group->getLabel() : null))
{{ $recordGroupLabel }}:
@endif
{{ $recordGroupTitle }}
</{{ $secondLevelHeadingTag }}>
@if (filled($recordGroupDescription = $group->getDescription($record, $recordGroupTitle)))
<p class="fi-ta-group-description">
{{ $recordGroupDescription }}
</p>
@endif
</div>
@if ($isRecordGroupCollapsible)
<button
aria-label="{{ filled($recordGroupLabel) ? $recordGroupLabel . ': ' . $recordGroupTitle : $recordGroupTitle }}"
x-bind:aria-expanded="!isGroupCollapsed(@js($recordGroupTitle))"
type="button" class="fi-icon-btn fi-size-sm">
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::ChevronUp, alias: \Filament\Tables\View\TablesIconAlias::GROUPING_COLLAPSE_BUTTON, size: \Filament\Support\Enums\IconSize::Small) }}
</button>
@endif
</div>
</td>
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::AfterCells)
<td class="fi-ta-cell fi-ta-group-selection-cell">
@if ($maxSelectableRecords !== 1)
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_group.label', ['title' => $recordGroupTitle]) }}"
type="checkbox"
data-group-selectable-record-keys="{{ json_encode($this->getGroupedSelectableTableRecordKeys($recordGroupKey)) }}"
@if ($isSelectionDisabled) disabled
@else
x-on:click="toggleSelectRecords(JSON.parse($el.dataset.groupSelectableRecordKeys))"
@if ($maxSelectableRecords)
x-bind:disabled="
const recordsInGroup = JSON.parse($el.dataset.groupSelectableRecordKeys)
return recordsInGroup.length && ! areRecordsToggleable(recordsInGroup)
" @endif
@endif
x-bind:checked="const recordsInGroup = JSON.parse($el.dataset
.groupSelectableRecordKeys)
if (recordsInGroup.length && areRecordsSelected(
recordsInGroup)) {
$el.checked = true
return 'checked'
}
$el.checked = false
return null"
wire:key="{{ $this->getId() }}.table.bulk_select_group.checkbox.{{ $page }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-group-checkbox fi-checkbox-input"
/>
@endif
</td>
@endif
</tr>
@endif
@php
$isRecordRowStriped = false;
@endphp
@endif
@if (!$isGroupsOnly)
<tr wire:key="{{ $this->getId() }}.table.records.{{ $recordKey }}"
{{ $isReordering ? 'x-sortable-handle' : null }}
{!! $isReordering ? 'x-sortable-item="' . e($recordKey) . '"' : null !!}
x-bind:class="{
{{ $group?->isCollapsible() ? '\'fi-collapsed\': isGroupCollapsed(' . \Illuminate\Support\Js::from($recordGroupTitle) . '),' : '' }}
'fi-selected': isRecordSelected(
@js($recordKey)),
}"
@class([
'fi-ta-row',
'fi-clickable' => $recordAction || $recordUrl,
'fi-striped' => $isStriped && $isRecordRowStriped,
...$getRecordClasses($record),
])>
@if ($isReordering)
<td class="fi-ta-cell">
<button class="fi-ta-reorder-handle fi-icon-btn"
type="button">
{{ \Filament\Support\generate_icon_html(\Filament\Support\Icons\Heroicon::Bars2, alias: \Filament\Tables\View\TablesIconAlias::REORDER_HANDLE) }}
</button>
</td>
@endif
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::BeforeCells && !$isReordering)
<td class="fi-ta-cell">
<div @class([
'fi-ta-actions',
match ($recordActionsAlignment) {
Alignment::Center => 'fi-align-center',
Alignment::Start, Alignment::Left => 'fi-align-start',
Alignment::Between, Alignment::Justify => 'fi-align-between',
Alignment::End, Alignment::Right => '',
default => is_string($recordActionsAlignment)
? $recordActionsAlignment
: '',
},
])>
@foreach ($recordActions as $action)
{{ $action }}
@endforeach
</div>
</td>
@endif
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::BeforeCells && !$isReordering)
<td class="fi-ta-cell fi-ta-selection-cell">
@if ($isRecordSelectable($record))
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_record.label', ['key' => $recordKey]) }}"
type="checkbox"
@if ($isSelectionDisabled) disabled
@elseif ($maxSelectableRecords && $maxSelectableRecords !== 1)
x-bind:disabled="! areRecordsToggleable([@js($recordKey)])" @endif
value="{{ $recordKey }}"
x-on:click="toggleSelectedRecord(@js($recordKey))"
x-bind:checked="isRecordSelected(@js($recordKey)) ?
'checked' : null"
data-group="{{ $recordGroupKey }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-record-checkbox fi-checkbox-input" />
@endif
</td>
@endif
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::BeforeColumns && !$isReordering)
<td class="fi-ta-cell">
<div @class([
'fi-ta-actions',
match ($recordActionsAlignment) {
Alignment::Center => 'fi-align-center',
Alignment::Start, Alignment::Left => 'fi-align-start',
Alignment::Between, Alignment::Justify => 'fi-align-between',
Alignment::End, Alignment::Right => '',
default => is_string($recordActionsAlignment)
? $recordActionsAlignment
: '',
},
])>
@foreach ($recordActions as $action)
{{ $action }}
@endforeach
</div>
</td>
@endif
@foreach ($columns as $column)
@php
$column->record($record);
$column->rowLoop($loop->parent);
$column->recordKey($recordKey);
$columnAction = $column->getAction();
$columnUrl = $column->getUrl();
$columnHasStateBasedUrls = $column->hasStateBasedUrls();
$isColumnClickDisabled =
$column->isClickDisabled() || $isReordering;
$columnWrapperTag = match (true) {
($columnUrl ||
($recordUrl && $columnAction === null)) &&
!$columnHasStateBasedUrls &&
!$isColumnClickDisabled
=> 'a',
($columnAction || $recordAction) &&
!$columnHasStateBasedUrls &&
!$isColumnClickDisabled
=> 'button',
default => 'div',
};
if ($columnWrapperTag === 'button') {
if ($columnAction instanceof \Filament\Actions\Action) {
$columnWireClickAction = "mountTableAction('{$columnAction->getName()}', '{$recordKey}')";
} elseif ($columnAction) {
$columnWireClickAction = "callTableColumnAction('{$column->getName()}', '{$recordKey}')";
} else {
if ($this->getTable()->getAction($recordAction)) {
$columnWireClickAction = "mountTableAction('{$recordAction}', '{$recordKey}')";
} else {
$columnWireClickAction = "{$recordAction}('{$recordKey}')";
}
}
}
@endphp
<td wire:key="{{ $this->getId() }}.table.record.{{ $recordKey }}.column.{{ $column->getName() }}"
{{ $column->getExtraCellAttributeBag()->class([
'fi-ta-cell',
'fi-ta-cell-' . str($column->getName())->camel()->kebab(),
($columnAlignment = $column->getAlignment()) instanceof \Filament\Support\Enums\Alignment
? "fi-align-{$columnAlignment->value}"
: (is_string($columnAlignment)
? $columnAlignment
: ''),
($columnVerticalAlignment = $column->getVerticalAlignment()) instanceof
\Filament\Support\Enums\VerticalAlignment
? "fi-vertical-align-{$columnVerticalAlignment->value}"
: (is_string($columnVerticalAlignment)
? $columnVerticalAlignment
: ''),
filled($columnHiddenFrom = $column->getHiddenFrom()) ? "{$columnHiddenFrom}:fi-hidden" : '',
filled($columnVisibleFrom = $column->getVisibleFrom()) ? "{$columnVisibleFrom}:fi-visible" : '',
]) }}>
{!! $isStackedOnMobile
? '<div class="fi-ta-cell-label">' . e($column->getLabel()) . '</div><div class="fi-ta-cell-content">'
: '' !!}
<{{ $columnWrapperTag }}
@if ($columnWrapperTag === 'a') {{ \Filament\Support\generate_href_html($columnUrl ?: $recordUrl, $columnUrl ? $column->shouldOpenUrlInNewTab() : $openRecordUrlInNewTab, hasNestedClickEventHandler: true) }}
@if (blank($columnUrl) && filled($recordUrl))
{{ $getExtraRecordLinkAttributeBag($record) }} @endif
@elseif ($columnWrapperTag === 'button') type="button"
wire:click.prevent.stop="{{ $columnWireClickAction }}"
wire:loading.attr="disabled"
wire:target="{{ $columnWireClickAction }}"
@endif
@class([
'fi-ta-col',
'fi-ta-col-has-column-url' =>
$columnWrapperTag === 'a' && filled($columnUrl),
])
>
{{ $column }}
</{{ $columnWrapperTag }}>
{!! $isStackedOnMobile ? '</div>' : '' !!}
</td>
@endforeach
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::AfterColumns && !$isReordering)
<td class="fi-ta-cell">
<div @class([
'fi-ta-actions',
match ($recordActionsAlignment) {
Alignment::Center => 'fi-align-center',
Alignment::Start, Alignment::Left => 'fi-align-start',
Alignment::Between, Alignment::Justify => 'fi-align-between',
Alignment::End, Alignment::Right => '',
default => is_string($recordActionsAlignment)
? $recordActionsAlignment
: '',
},
])>
@foreach ($recordActions as $action)
{{ $action }}
@endforeach
</div>
</td>
@endif
@if ($isSelectionEnabled && $recordCheckboxPosition === RecordCheckboxPosition::AfterCells && !$isReordering)
<td class="fi-ta-cell fi-ta-selection-cell">
@if ($isRecordSelectable($record))
<input
aria-label="{{ __('filament-tables::table.fields.bulk_select_record.label', ['key' => $recordKey]) }}"
type="checkbox"
@if ($isSelectionDisabled) disabled
@elseif ($maxSelectableRecords && $maxSelectableRecords !== 1)
x-bind:disabled="! areRecordsToggleable([@js($recordKey)])" @endif
value="{{ $recordKey }}"
x-on:click="toggleSelectedRecord(@js($recordKey))"
x-bind:checked="isRecordSelected(@js($recordKey)) ?
'checked' : null"
data-group="{{ $recordGroupKey }}"
wire:loading.attr="disabled"
wire:target="{{ implode(',', \Filament\Tables\Table::LOADING_TARGETS) }}"
class="fi-ta-record-checkbox fi-checkbox-input" />
@endif
</td>
@endif
@if (count($defaultRecordActions) && $recordActionsPosition === RecordActionsPosition::AfterCells && !$isReordering)
<td class="fi-ta-cell">
<div @class([
'fi-ta-actions',
match ($recordActionsAlignment) {
Alignment::Center => 'fi-align-center',
Alignment::Start, Alignment::Left => 'fi-align-start',
Alignment::Between, Alignment::Justify => 'fi-align-between',
Alignment::End, Alignment::Right => '',
default => is_string($recordActionsAlignment)
? $recordActionsAlignment
: '',
},
])>
@foreach ($recordActions as $action)
{{ $action }}
@endforeach
</div>
</td>
@endif
</tr>
@endif
@php
$isRecordRowStriped = !$isRecordRowStriped;
$previousRecord = $record;
$previousRecordGroupKey = $recordGroupKey;
$previousRecordGroupTitle = $recordGroupTitle;
@endphp
@endforeach
@if (
$hasSummary &&
!$isReordering &&
filled($previousRecordGroupTitle) &&
$this->shouldRenderTrailingGroupedTableSummary($previousRecord))
@php
$groupColumn = $group->getColumn();
$groupScopedAllTableSummaryQuery = $group->scopeQuery(
$this->getAllTableSummaryQuery(),
$previousRecord,
);
@endphp
<x-filament-tables::summary.row :actions="count($defaultRecordActions)" :actions-position="$recordActionsPosition"
:columns="$columns" :group-column="$groupColumn" :groups-only="$isGroupsOnly"
:heading="$isGroupsOnly
? $previousRecordGroupTitle
: __('filament-tables::table.summary.subheadings.group', [
'group' => $previousRecordGroupTitle,
'label' => $pluralModelLabel,
])" :query="$groupScopedAllTableSummaryQuery" :record-checkbox-position="$recordCheckboxPosition"
:selected-state="$groupedSummarySelectedState[$previousRecordGroupKey] ?? []" :selection-enabled="$isSelectionEnabled" />
@endif
@if ($hasSummary && !$isReordering)
@php
$groupColumn = $group?->getColumn();
@endphp
<x-filament-tables::summary :actions="count($defaultRecordActions)" :actions-position="$recordActionsPosition"
:all-table-summary="$hasAllTableSummary" :columns="$columns" :group-column="$groupColumn"
:groups-only="$isGroupsOnly" :page-summary="$hasPageSummary" :plural-model-label="$pluralModelLabel"
:record-checkbox-position="$recordCheckboxPosition" :records="$records" :selection-enabled="$isSelectionEnabled" />
@endif
@endif
</tbody>
@endif
@if ($records !== null && count($records) && $contentFooter)
<tfoot>
<tr>
{{ $contentFooter->with([
'columns' => $columns,
'records' => $records,
]) }}
</tr>
</tfoot>
@endif
</table>
@elseif ($records === null)
<div class="fi-ta-table-loading-ctn">
{{ \Filament\Support\generate_loading_indicator_html(size: \Filament\Support\Enums\IconSize::TwoExtraLarge) }}
</div>
@endif
</div>
@endif
@if ($hasEmptyState)
@if ($emptyState = $getEmptyState())
{{ $emptyState }}
@else
<div class="fi-ta-empty-state">
<div class="fi-ta-empty-state-content">
<div class="fi-ta-empty-state-icon-bg">
{{ \Filament\Support\generate_icon_html($getEmptyStateIcon(), size: \Filament\Support\Enums\IconSize::Large) }}
</div>
<{{ $secondLevelHeadingTag }} class="fi-ta-empty-state-heading">
{{ $getEmptyStateHeading() }}
</{{ $secondLevelHeadingTag }}>
@if (filled($emptyStateDescription = $getEmptyStateDescription()))
<p class="fi-ta-empty-state-description">
{{ $emptyStateDescription }}
</p>
@endif
@if (
$emptyStateActions = array_filter(
$getEmptyStateActions(),
fn(\Filament\Actions\Action|\Filament\Actions\ActionGroup $action): bool => $action->isVisible()))
<div class="fi-ta-actions fi-align-center fi-wrapped">
@foreach ($emptyStateActions as $action)
{{ $action }}
@endforeach
</div>
@endif
</div>
</div>
@endif
@endif
@if ($hasPagination)
@php
$hasExtremePaginationLinks = $hasExtremePaginationLinks();
$paginationPageOptions = $getPaginationPageOptions();
@endphp
<x-filament::pagination :extreme-links="$hasExtremePaginationLinks" :page-options="$paginationPageOptions" :paginator="$records" />
@endif
@if ($hasFiltersBelowContent)
<x-filament-tables::filters :apply-action="$filtersApplyAction" :form="$filtersForm" :heading-tag="$secondLevelHeadingTag"
class="fi-ta-filters-below-content" :reset-action-position="$filtersResetActionPosition" />
@endif
</div>
@if ($hasFiltersAfterContent)
<div wire:ignore.self x-ref="filtersContentContainer" x-transition:enter-start="fi-opacity-0"
x-transition:leave-end="fi-opacity-0" x-bind:class="{ 'fi-open': areFiltersOpen }"
@class([
'fi-ta-filters-after-content-ctn',
'lg:fi-open' => !$hasCollapsibleFilters,
($filtersFormWidth ??= Width::ExtraSmall) instanceof Width
? "fi-width-{$filtersFormWidth->value}"
: (is_string($filtersFormWidth)
? $filtersFormWidth
: null),
])>
<x-filament-tables::filters :apply-action="$filtersApplyAction" :form="$filtersForm" :heading-tag="$secondLevelHeadingTag"
class="fi-ta-filters-after-content" :reset-action-position="$filtersResetActionPosition" />
</div>
@endif
</div>
<x-filament-actions::modals />
</div>