@props([ 'idPrefix' => 'ajax', 'title' => 'Tabela AJAX', 'routes' => [], 'columns' => [], 'filters' => [], 'tableSettingsKey' => null, 'closeHref' => null, 'closeButtonTitle' => 'Zamknij widok', 'showExport' => true, 'showCloseButton' => true, 'searchPlaceholder' => 'Wpisz tekst aby wyszukać ...', 'searchLabel' => 'Wyszukaj:', 'searchColumnsLabel' => 'Wybierz kolumny do przeszukania.', 'columnsSelectorLabel' => 'Wybierz kolumny do wyświetlenia na widoku.', 'searchColumnsButtonClass' => 'btn btn-secondary', 'columnSelectorCounter' => false, 'defaultPerPage' => 15, 'perPageStorageKey' => null, 'columnStorageKey' => null, 'columnOrderStorageKey' => null, 'searchColumnStorageKey' => null, 'actionColumnWidth' => '9rem', 'rowIdKey' => 'id', 'activeRowValue' => null, 'modals' => [], 'actions' => ['info' => true, 'edit' => true, 'delete' => true], 'actionOrder' => [], 'actionButtons' => [], 'messages' => [], 'settingsGetUrl' => null, 'settingsSetUrl' => null, 'filtersEventName' => null, 'emptyText' => 'Brak rekordów spełniających wybrany warunek.', 'loadingText' => 'Ładowanie...', 'errorText' => 'Wystąpił błąd.', ]) @php $idPrefix = trim((string) $idPrefix); if ($idPrefix === '') { $idPrefix = 'ajax'; } $tableSettingsKey = trim((string) ($tableSettingsKey ?: $idPrefix)); $settingsGetUrl = $settingsGetUrl ?: route('user_settings_app_get'); $settingsSetUrl = $settingsSetUrl ?: route('user_settings_app_set'); $filtersEventName = $filtersEventName ?: ($idPrefix . '-filters:changed'); $requestBasePath = rtrim((string) request()->getBaseUrl(), '/'); $normalizeRouteForCurrentAppBase = function ($routeUrl) use ($requestBasePath) { $url = trim((string) $routeUrl); if ($url === '') { return ''; } if (preg_match('/^(mailto:|tel:|javascript:)/i', $url)) { return $url; } $parts = @parse_url($url); if (!is_array($parts)) { return $url; } $path = (string) ($parts['path'] ?? ''); $query = isset($parts['query']) ? ('?' . $parts['query']) : ''; $fragment = isset($parts['fragment']) ? ('#' . $parts['fragment']) : ''; if ($path === '') { return $url; } if ($path[0] !== '/') { $path = '/' . $path; } if ($requestBasePath !== '' && strpos($path, $requestBasePath . '/') !== 0 && $path !== $requestBasePath) { $path = $requestBasePath . '/' . ltrim($path, '/'); } return $path . $query . $fragment; }; $routes = collect((array) $routes) ->map(function ($routeUrl) use ($normalizeRouteForCurrentAppBase) { return $normalizeRouteForCurrentAppBase($routeUrl); }) ->all(); $settingsGetUrl = $normalizeRouteForCurrentAppBase($settingsGetUrl); $settingsSetUrl = $normalizeRouteForCurrentAppBase($settingsSetUrl); $perPageStorageKey = $perPageStorageKey ?: ($idPrefix . '_per_page'); $columnStorageKey = $columnStorageKey ?: ($idPrefix . '_visible_columns'); $columnOrderStorageKey = $columnOrderStorageKey ?: ($idPrefix . '_column_order'); $searchColumnStorageKey = $searchColumnStorageKey ?: ($idPrefix . '_search_columns'); $recordKey = trim((string) $rowIdKey); if ($recordKey === '') { $recordKey = 'id'; } $resolvedActions = [ 'info' => (bool) ($actions['info'] ?? true), 'edit' => (bool) ($actions['edit'] ?? true), 'delete' => (bool) ($actions['delete'] ?? true), ]; $normalizedActionButtons = collect((array) $actionButtons) ->map(function ($button, $index) use ($idPrefix) { $button = (array) $button; $rawKey = trim((string) ($button['key'] ?? ('custom_' . $index))); $safeKey = preg_replace('/[^A-Za-z0-9_-]/', '_', $rawKey); if ($safeKey === '') { $safeKey = 'custom_' . $index; } $handler = strtolower(trim((string) ($button['handler'] ?? 'event'))); if (!in_array($handler, ['event', 'url', 'modal'], true)) { $handler = 'event'; } return [ 'key' => $safeKey, 'title' => trim((string) ($button['title'] ?? ucfirst(str_replace('_', ' ', $safeKey)))), 'icon' => trim((string) ($button['icon'] ?? 'fas fa-circle')), 'button_class' => trim((string) ($button['button_class'] ?? 'btn btn_table new-btn-border-1 new-btn-secondary')), 'handler' => $handler, 'event_name' => trim((string) ($button['event_name'] ?? ($idPrefix . ':action:' . $safeKey))), 'url' => trim((string) ($button['url'] ?? '')), 'target' => trim((string) ($button['target'] ?? '_self')), 'modal_selector' => trim((string) ($button['modal_selector'] ?? '')), 'enabled' => array_key_exists('enabled', $button) ? (bool) $button['enabled'] : true, ]; }) ->filter(function ($button) { return (bool) ($button['enabled'] ?? true); }) ->values() ->all(); $normalizedActionOrder = collect((array) $actionOrder) ->map(function ($item) { return trim((string) $item); }) ->filter(function ($item) { return $item !== ''; }) ->values() ->all(); $messages = array_merge([ 'load_error' => 'Błąd podczas pobierania danych.', 'load_failed' => 'Nie udało się pobrać listy.', 'export_failed' => 'Nie udało się przygotować eksportu.', 'search_columns_required' => 'Wybierz co najmniej jedną widoczną kolumnę do wyszukiwania.', 'visible_columns_required' => 'Co najmniej jedna kolumna musi pozostać widoczna.', 'record_not_found' => 'Nie znaleziono danych rekordu.', 'missing_id' => 'Brak identyfikatora rekordu.', 'save_error' => 'Błąd zapisu danych.', 'delete_error' => 'Błąd podczas usuwania rekordu.', ], (array) $messages); $normalizedColumns = collect((array) $columns) ->map(function ($column) { $key = trim((string) ($column['key'] ?? '')); if ($key === '') { return null; } $label = trim((string) ($column['label'] ?? $key)); $field = trim((string) ($column['field'] ?? $key)); $sort = trim((string) ($column['sort'] ?? '')); $searchField = trim((string) ($column['search_field'] ?? '')); $align = strtolower(trim((string) ($column['align'] ?? 'left'))); $width = trim((string) ($column['width'] ?? '')); if (!in_array($align, ['left', 'center', 'right'], true)) { $align = 'left'; } return [ 'key' => $key, 'label' => $label, 'field' => $field, 'sort' => $sort, 'search_field' => $searchField, 'align' => $align, 'width' => $width, 'checked' => array_key_exists('checked', $column) ? (bool) $column['checked'] : true, 'truncate' => isset($column['truncate']) ? (int) $column['truncate'] : 0, 'nowrap' => (bool) ($column['nowrap'] ?? false), ]; }) ->filter() ->values(); if ($normalizedColumns->isEmpty()) { $normalizedColumns = collect([ [ 'key' => 'id', 'label' => 'ID', 'field' => 'id', 'sort' => 'id', 'search_field' => 'id', 'align' => 'center', 'width' => '6rem', 'checked' => true, 'truncate' => 0, 'nowrap' => false, ], ]); } $columnMeta = []; $columnOptions = []; $searchColumnsOptions = []; $defaultVisibleColumns = []; $defaultSearchColumns = []; $defaultColumnOrder = []; foreach ($normalizedColumns as $column) { $key = $column['key']; $forcedAlign = $column['align']; if (strtolower($key) === 'id') { $forcedAlign = 'center'; } $alignClass = 'text-left'; if ($forcedAlign === 'center') { $alignClass = 'text-center'; } elseif ($forcedAlign === 'right') { $alignClass = 'text-right'; } $defaultVisibleColumns[$key] = (bool) $column['checked']; $defaultColumnOrder[] = $key; $columnMeta[$key] = [ 'field' => $column['field'], 'sort' => $column['sort'], 'search_field' => $column['search_field'], 'align' => $forcedAlign, 'align_class' => $alignClass, 'width' => $column['width'], 'truncate' => (int) $column['truncate'], 'nowrap' => (bool) $column['nowrap'], ]; $columnOptions[] = [ 'key' => $key, 'label' => $column['label'], 'checked' => (bool) $column['checked'], ]; if ($column['search_field'] !== '') { $searchColumnsOptions[] = [ 'key' => $key, 'label' => $column['label'], 'checked' => true, ]; $defaultSearchColumns[$key] = true; } } if (collect($defaultVisibleColumns)->filter()->isEmpty()) { foreach ($defaultVisibleColumns as $key => $value) { $defaultVisibleColumns[$key] = true; } } if (empty($defaultSearchColumns)) { foreach ($defaultVisibleColumns as $key => $value) { $defaultSearchColumns[$key] = true; } } $defaultSort = null; // 1) Priorytet: kolumna o kluczu "id" (jeżeli ma zdefiniowane sortowanie). foreach ($normalizedColumns as $column) { if (strtolower((string) ($column['key'] ?? '')) === 'id' && $column['sort'] !== '') { $defaultSort = $column['sort']; break; } } // 2) Fallback: pierwsza kolumna z sortowaniem. if ($defaultSort === null) { foreach ($normalizedColumns as $column) { if ($column['sort'] !== '') { $defaultSort = $column['sort']; break; } } } // 3) Ostateczny fallback. if ($defaultSort === null) { $defaultSort = 'id'; } $filterIdMap = []; $defaultFilterState = []; foreach ((array) $filters as $idx => $filter) { $key = trim((string) ($filter['key'] ?? '')); if ($key === '') { continue; } $filterIdMap[$key] = (string) ($filter['id'] ?? ($idPrefix . '_filter_' . $idx)); $defaultFilterState[$key] = (string) ($filter['value'] ?? ''); } $hasDefinedFilters = !empty($defaultFilterState); $searchToolbarColClass = $hasDefinedFilters ? 'col-md-4 row text-right' : 'col-md-12 row text-right'; $searchColumnDbMap = []; foreach ($columnMeta as $key => $meta) { if (!empty($meta['search_field'])) { $searchColumnDbMap[$key] = $meta['search_field']; } } $listRoute = (string) ($routes['list'] ?? ''); $storeRoute = (string) ($routes['store'] ?? ''); $updateRoute = (string) ($routes['update'] ?? ''); $deleteRoute = (string) (($routes['delete'] ?? '') ?: ($routes['del'] ?? '')); $exportRoute = (string) ($routes['export'] ?? ''); $modals = array_merge([ 'add' => [ 'modal_selector' => '#ModalForm_' . $idPrefix . '_add', 'form_selector' => '#' . $idPrefix . '-form-add', 'reset_fields' => [], ], 'edit' => [ 'modal_selector' => '#ModalForm_' . $idPrefix . '_edit', 'form_selector' => '#' . $idPrefix . '-form-edit', 'id_selector' => '#' . $idPrefix . '_edit_id', 'record_id_selector' => '#edit_rekord_id', 'record_id_display_selector' => '[data-role="edit-record-id-display"]', 'fields' => [], ], 'info' => [ 'modal_selector' => '#ModalForm_' . $idPrefix . '_info', 'fields' => [], ], 'delete' => [ 'modal_selector' => '#ModalForm_' . $idPrefix . '_delete', 'form_selector' => '#ModalForm_' . $idPrefix . '_delete form#form_delete', 'id_selector' => 'input[name="rekord_id"]', 'id_view_selector' => 'input[name="rekord_id_view"]', ], ], (array) $modals); $activeRowValue = $activeRowValue ?? (session()->get('active_record_'.session()->get('function_id')) ?? '0'); $tableId = $idPrefix . '-table'; $tableBodyId = $idPrefix . '-table-body'; $searchInputId = $idPrefix . '-search'; $searchBtnId = $idPrefix . '-search-btn'; $searchClearId = $idPrefix . '-search-clear'; $searchColumnsId = $idPrefix . '-search-columns'; $columnsSelectorId = $idPrefix . '-columns'; $filtersId = $idPrefix . '-filters'; $filtersStatusId = $idPrefix . '-filters-status'; $countId = $idPrefix . '-count'; $pagePrevId = $idPrefix . '-page-prev'; $pageNextId = $idPrefix . '-page-next'; $pageListId = $idPrefix . '-page-list'; $pageInfoId = $idPrefix . '-page-info'; $perPageId = $idPrefix . '-per-page'; $exportToolbarId = $idPrefix . '-export-toolbar'; $closeButtonId = $idPrefix . '-close-view-btn'; $activeRowInputId = $idPrefix . '-active-row'; $addButtonClass = $idPrefix . '-btn-add-trigger'; $editButtonClass = $idPrefix . '-btn-edit'; $infoButtonClass = $idPrefix . '-btn-info'; $deleteButtonClass = $idPrefix . '-btn-delete'; $customActionButtonClass = $idPrefix . '-btn-custom-action'; $sortIconClass = $idPrefix . '-sort'; $pageBtnClass = $idPrefix . '-page-btn'; $rowColspan = $normalizedColumns->count() + 2; $tableSettingsLocalStorageKey = 'user_settings_app_' . $tableSettingsKey . '_u' . (int) (Auth::id() ?? 0); $jsStateTemplate = [ 'page' => 1, 'per_page' => (int) $defaultPerPage, 'search' => '', 'search_columns' => $defaultSearchColumns, 'filters' => $defaultFilterState, 'sort' => $defaultSort, 'direction' => 'asc', 'active_row_id' => '', 'active_row_scroll_pending' => false, 'records' => [], 'pagination' => null, 'visible_columns' => $defaultVisibleColumns, 'column_order' => $defaultColumnOrder, ]; $componentConfig = [ 'idPrefix' => $idPrefix, 'csrfToken' => csrf_token(), 'tableSettingsKey' => $tableSettingsKey, 'tableSettingsLocalStorageKey' => $tableSettingsLocalStorageKey, 'routes' => [ 'list' => $listRoute, 'store' => $storeRoute, 'update' => $updateRoute, 'del' => $deleteRoute, 'export' => $exportRoute, 'settings_get' => $settingsGetUrl, 'settings_set' => $settingsSetUrl, ], 'storage' => [ 'perPage' => $perPageStorageKey, 'columns' => $columnStorageKey, 'columnOrder' => $columnOrderStorageKey, 'searchColumns' => $searchColumnStorageKey, ], 'stateTemplate' => $jsStateTemplate, 'columnMeta' => $columnMeta, 'filterIdMap' => $filterIdMap, 'searchColumnDbMap' => $searchColumnDbMap, 'modals' => $modals, 'actions' => $resolvedActions, 'actionOrder' => $normalizedActionOrder, 'actionButtons' => $normalizedActionButtons, 'messages' => $messages, 'selectors' => [ 'table' => '#' . $tableId, 'tableBody' => '#' . $tableBodyId, 'activeRowInput' => '#' . $activeRowInputId, 'searchInput' => '#' . $searchInputId, 'searchBtn' => '#' . $searchBtnId, 'searchClearBtn' => '#' . $searchClearId, 'filtersId' => $filtersId, 'filtersEvent' => $filtersEventName, 'count' => '#' . $countId, 'pagePrev' => '#' . $pagePrevId, 'pageNext' => '#' . $pageNextId, 'pageList' => '#' . $pageListId, 'pageInfo' => '#' . $pageInfoId, 'pageBtnClass' => $pageBtnClass, 'perPage' => '#' . $perPageId, 'columnsToggleClass' => $columnsSelectorId . '-toggle', 'columnsListClass' => $columnsSelectorId . '-list', 'columnsItemClass' => $columnsSelectorId . '-item', 'columnsResetClass' => $columnsSelectorId . '-reset', 'searchColumnsToggleClass' => $searchColumnsId . '-toggle', 'searchColumnsResetClass' => $searchColumnsId . '-reset', 'sortIconClass' => $sortIconClass, 'addButtonClass' => $addButtonClass, 'editButtonClass' => $editButtonClass, 'infoButtonClass' => $infoButtonClass, 'deleteButtonClass' => $deleteButtonClass, 'customActionButtonClass' => $customActionButtonClass, 'exportToolbar' => '#' . $exportToolbarId, ], 'columnsComponentId' => $columnsSelectorId, 'searchColumnsComponentId' => $searchColumnsId, 'row' => [ 'key' => $recordKey, 'colspan' => (int) $rowColspan, ], 'text' => [ 'empty' => $emptyText, 'loading' => $loadingText, 'error' => $errorText, ], 'setSessionUrl' => url('/set_session'), ]; $ajaxCrudScriptPath = public_path('js/ajax_crud_table_component.js'); $ajaxCrudScriptUrl = asset('js/ajax_crud_table_component.js'); if (file_exists($ajaxCrudScriptPath)) { $ajaxCrudScriptUrl .= '?v=' . filemtime($ajaxCrudScriptPath); } @endphp