diff --git a/app/Grid/Renderer/TwigBulkActionGridRenderer.php b/app/Grid/Renderer/TwigBulkActionGridRenderer.php new file mode 100644 index 00000000..43587725 --- /dev/null +++ b/app/Grid/Renderer/TwigBulkActionGridRenderer.php @@ -0,0 +1,58 @@ +getType(); + if (!isset($this->bulkActionTemplates[$type])) { + throw new \InvalidArgumentException(sprintf('Missing template for bulk action type "%s".', $type)); + } + + $options = $this->optionsParser->parseOptions( + $bulkAction->getOptions(), + $this->requestStack?->getCurrentRequest() ?? new Request(), + $data, + ); + + return $this->twig->render($this->bulkActionTemplates[$type], [ + 'grid' => $gridView, + 'action' => $bulkAction, + 'data' => $data, + 'options' => $options, + ]); + } +} diff --git a/app/Grid/Renderer/TwigGridRenderer.php b/app/Grid/Renderer/TwigGridRenderer.php new file mode 100644 index 00000000..2e1e5474 --- /dev/null +++ b/app/Grid/Renderer/TwigGridRenderer.php @@ -0,0 +1,94 @@ +gridRenderer = $gridRenderer; + $this->twig = $twig; + $this->optionsParser = $optionsParser; + $this->actionTemplates = $actionTemplates; + } + + public function render(GridViewInterface $gridView, ?string $template = null): string + { + return $this->gridRenderer->render($gridView, $template); + } + + /** + * @param mixed $data + */ + public function renderField(GridViewInterface $gridView, Field $field, $data): string + { + return $this->gridRenderer->renderField($gridView, $field, $data); + } + + /** + * @param mixed $data + */ + public function renderAction(GridViewInterface $gridView, Action $action, $data = null): string + { + $type = $action->getType(); + if (!isset($this->actionTemplates[$type])) { + throw new \InvalidArgumentException(sprintf('Missing template for action type "%s".', $type)); + } + + $options = $this->optionsParser->parseOptions( + $action->getOptions(), + $this->requestStack?->getCurrentRequest() ?? new Request(), + $data, + ); + + return $this->twig->render($this->actionTemplates[$type], [ + 'grid' => $gridView, + 'action' => $action, + 'data' => $data, + 'options' => $options, + ]); + } + + public function renderFilter(GridViewInterface $gridView, Filter $filter): string + { + return $this->gridRenderer->renderFilter($gridView, $filter); + } +} diff --git a/app/Grid/SpeakerGrid.php b/app/Grid/SpeakerGrid.php index 6384256b..1c1e13d0 100644 --- a/app/Grid/SpeakerGrid.php +++ b/app/Grid/SpeakerGrid.php @@ -38,6 +38,7 @@ public static function getName(): string public function buildGrid(GridBuilderInterface $gridBuilder): void { $gridBuilder + ->setLimits([10, 25, 50]) ->addFilter( StringFilter::create('search', ['firstName', 'lastName', 'companyName']) ->setLabel('sylius.ui.search'), @@ -64,7 +65,12 @@ public function buildGrid(GridBuilderInterface $gridBuilder): void ) ->addActionGroup( MainActionGroup::create( - CreateAction::create(), + CreateAction::create() + ->setOptions([ + 'link' => [ + 'route' => 'app_admin_speaker_create', + ], + ]), ), ) ->addActionGroup( @@ -82,15 +88,23 @@ public function buildGrid(GridBuilderInterface $gridBuilder): void ], ], ]), - UpdateAction::create(), - DeleteAction::create(), - ), - ) - ->addActionGroup( - BulkActionGroup::create( - DeleteAction::create(), + UpdateAction::create() + ->setOptions([ + 'link' => [ + 'route' => 'app_admin_speaker_update', + 'parameters' => [ + 'id' => 'resource.id', + ], + ], + ]), +// DeleteAction::create(), ), ) +// ->addActionGroup( +// BulkActionGroup::create( +// DeleteAction::create(), +// ), +// ) ; } diff --git a/app/Grid/TalkGrid.php b/app/Grid/TalkGrid.php index 641984a0..9c2a3609 100644 --- a/app/Grid/TalkGrid.php +++ b/app/Grid/TalkGrid.php @@ -92,17 +92,17 @@ public function buildGrid(GridBuilderInterface $gridBuilder): void CreateAction::create(), ), ) - ->addActionGroup( - ItemActionGroup::create( - UpdateAction::create(), - DeleteAction::create(), - ), - ) - ->addActionGroup( - BulkActionGroup::create( - DeleteAction::create(), - ), - ) +// ->addActionGroup( +// ItemActionGroup::create( +// UpdateAction::create(), +// DeleteAction::create(), +// ), +// ) +// ->addActionGroup( +// BulkActionGroup::create( +// DeleteAction::create(), +// ), +// ) ; } diff --git a/app/Twig/Component/Grid/DataTableComponent.php b/app/Twig/Component/Grid/DataTableComponent.php new file mode 100644 index 00000000..cb659d11 --- /dev/null +++ b/app/Twig/Component/Grid/DataTableComponent.php @@ -0,0 +1,137 @@ +|null */ + #[LiveProp(writable: true)] + public ?array $criteria = null; + + /** @var array|null */ + #[LiveProp(writable: true)] + public ?array $sorting = null; + + #[LiveProp(writable: true)] + public ?int $limit = null; + + #[LiveProp(writable: true)] + public bool $pushOnBrowserHistory = true; + + private ?Request $request; + + public function __construct( + #[Autowire(service: ChainProvider::class)] + private readonly GridProviderInterface $gridProvider, + private readonly GridViewFactoryInterface $gridViewFactory, + ) { + } + + public function getResources(): GridViewInterface + { + if (null === $this->grid) { + throw new \RuntimeException('No Grid has been passed to the component.'); + } + + $gridDefinition = $this->gridProvider->get($this->grid); + + $config = ['page' => $this->page]; + + if (null !== $this->criteria) { + $config['criteria'] = $this->criteria; + } + + if (null !== $this->sorting) { + $config['sorting'] = $this->sorting; + } + + $gridView = $this->gridViewFactory->create( + $gridDefinition, + new Parameters($config), + ); + + $data = $gridView->getData(); + + if ($data instanceof PagerfantaInterface) { + $data->setCurrentPage($this->page); + + if (null !== $this->limit) { + $data->setMaxPerPage($this->limit); + } + } + + return $gridView; + } + + #[LiveAction] + public function sortBy(#[LiveArg] string $field, #[LiveArg] string $order): void + { + $this->sorting = [$field => $order]; + } + + #[LiveAction] + public function updateLimit(#[LiveArg] int $limit): void + { + $this->page = 1; + $this->criteria = null; + $this->sorting = null; + $this->limit = $limit; + } + + #[LiveAction] + public function changePage(#[LiveArg] int $page): void + { + $this->page = $page; + } + + #[LiveAction] + public function previousPage(): void + { + --$this->page; + } + + #[LiveAction] + public function nextPage(): void + { + ++$this->page; + } +} diff --git a/assets/app.js b/assets/app.js index 9c301b51..82d8fc1c 100644 --- a/assets/app.js +++ b/assets/app.js @@ -1 +1,2 @@ +import './bootstrap.js'; import './scripts/statistics_chart.js'; diff --git a/assets/bootstrap.js b/assets/bootstrap.js new file mode 100644 index 00000000..d4e50c91 --- /dev/null +++ b/assets/bootstrap.js @@ -0,0 +1,5 @@ +import { startStimulusApp } from '@symfony/stimulus-bundle'; + +const app = startStimulusApp(); +// register any custom, 3rd party controllers here +// app.register('some_controller_name', SomeImportedController); diff --git a/assets/controllers.json b/assets/controllers.json index 5d6f0470..12ca7b28 100644 --- a/assets/controllers.json +++ b/assets/controllers.json @@ -5,9 +5,9 @@ "enabled": true, "fetch": "eager", "autoimport": { - "tom-select/dist/css/tom-select.default.css": true, + "tom-select/dist/css/tom-select.default.css": false, "tom-select/dist/css/tom-select.bootstrap4.css": false, - "tom-select/dist/css/tom-select.bootstrap5.css": false + "tom-select/dist/css/tom-select.bootstrap5.css": true } } }, diff --git a/assets/controllers/data_table_controller.js b/assets/controllers/data_table_controller.js new file mode 100644 index 00000000..36d98ba6 --- /dev/null +++ b/assets/controllers/data_table_controller.js @@ -0,0 +1,83 @@ +import { Controller } from '@hotwired/stimulus'; +import { getComponent } from '@symfony/ux-live-component'; + +export default class extends Controller { + static values = { + page: Number, + pushOnBrowserHistory: Boolean, + } + + async initialize() { + this.component = await getComponent(this.element); + + const dataTables = document.querySelectorAll('[data-sylius-data-table]'); + const basePath = window.location.pathname; + + dataTables.forEach((dataTable) => { + this.addLinkEventListener(dataTable); + }); + + this.component.on('render:finished', () => { + const currentPage = this.pageValue || 1; + dataTables.forEach((dataTable) => { + this.updateLinks(dataTable, basePath, currentPage); + }); + }); + } + + + addLinkEventListener(dataTable) { + dataTable.addEventListener('click', (event) => { + const link = event.target.closest('a[data-action]'); + if (link) { + event.preventDefault(); + } + + const href = link.getAttribute('href'); + if (!href) return; + + if (this.pushOnBrowserHistoryValue) { + // Push the new URL into the browser history + window.history.pushState({}, '', href); + } + }); + } + + updateLinks(dataTable, basePath, currentPage) { + const links = dataTable.querySelectorAll('a[data-action]'); + + links.forEach(link => { + const href = link.getAttribute('href'); + if (!href) return; + + // Parse the href into a full URL object + const url = new URL(href, window.location.origin); + + // Extract only the query string (e.g., ?page=3&limit=25) + let query = url.searchParams; + + if (link.rel === 'prev' || link.rel === 'next') { + query = this.updatePageQuery(query, link.rel, currentPage); + } + + // Build a new href using the given base path and the original query string + const newHref = basePath + '?' + query.toString(); + + // Replace the original href with the cleaned-up one + link.setAttribute('href', newHref); + }); + } + + updatePageQuery(query, rel, currentPage) { + + let newPage = rel === 'prev' ? currentPage - 1 : currentPage + 1; + + // Ensure the page doesn't go below 1 + newPage = Math.max(newPage, 1); + + // Update the page query parameter + query.set('page', newPage); + + return query; + } +} diff --git a/config/sylius/twig_hooks/common/base.php b/config/sylius/twig_hooks/common/base.php index 801ae11f..f7ccc69f 100644 --- a/config/sylius/twig_hooks/common/base.php +++ b/config/sylius/twig_hooks/common/base.php @@ -16,11 +16,20 @@ return static function (ContainerConfigurator $container): void { $container->extension('sylius_twig_hooks', [ 'hooks' => [ + 'sylius_admin.base#stylesheets' => [ + 'symfony_ux' => [ + 'enabled' => false, + ], + ], + 'sylius_admin.base#javascripts' => [ 'app' => [ 'priority' => 200, 'template' => 'base/javascripts/app.html.twig', ], + 'symfony_ux' => [ + 'enabled' => false, + ], ], ], ]); diff --git a/config/sylius/twig_hooks/speaker/index.php b/config/sylius/twig_hooks/speaker/index.php index 55bb5f6a..708880bf 100644 --- a/config/sylius/twig_hooks/speaker/index.php +++ b/config/sylius/twig_hooks/speaker/index.php @@ -26,6 +26,15 @@ ], ], ], + + 'sylius_admin.speaker.index.content.grid' => [ + 'data_table' => [ + 'component' => 'sylius_grid_data_table', + 'props' => [ + 'grid' => '@=_context.grid', + ], + ], + ], ], ]); }; diff --git a/config/sylius/twig_hooks/talk/index.php b/config/sylius/twig_hooks/talk/index.php new file mode 100644 index 00000000..56a83a7a --- /dev/null +++ b/config/sylius/twig_hooks/talk/index.php @@ -0,0 +1,29 @@ +extension('sylius_twig_hooks', [ + 'hooks' => [ + 'sylius_admin.talk.index.content.grid' => [ + 'data_table' => [ + 'component' => 'sylius_grid_data_table', + 'props' => [ + 'grid' => '@=_context.grid', + ], + ], + ], + ], + ]); +}; diff --git a/importmap.php b/importmap.php index 3b49af09..5399299c 100644 --- a/importmap.php +++ b/importmap.php @@ -22,10 +22,6 @@ 'apexcharts' => [ 'version' => '4.4.0', ], - 'tom-select/dist/css/tom-select.default.css' => [ - 'version' => '2.4.3', - 'type' => 'css', - ], '@symfony/stimulus-bundle' => [ 'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js', ], @@ -45,4 +41,8 @@ '@symfony/ux-live-component' => [ 'path' => './vendor/symfony/ux-live-component/assets/dist/live_controller.js', ], + 'tom-select/dist/css/tom-select.bootstrap5.css' => [ + 'version' => '2.4.3', + 'type' => 'css', + ], ]; diff --git a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid.html.twig b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid.html.twig index 9672fdf4..c4104aa8 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid.html.twig @@ -1,3 +1,8 @@ +{% set grid = hookable_metadata.context.resources.definition.code|default(null) %} +{% set page = app.request.query.getInt('page', 1) %} +{% set limit = app.request.query.has('limit') ? app.request.query.getInt('limit') : null %} +{% set criteria = app.request.query.has('criteria') ? app.request.query.all('criteria') : null %} +
{% hook 'grid' %} diff --git a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig index d53568b0..b7f9aa8c 100644 --- a/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig +++ b/src/BootstrapAdminUi/templates/shared/crud/index/content/grid/data_table.html.twig @@ -1,12 +1,16 @@ {% import '@SyliusBootstrapAdminUi/shared/helper/table.html.twig' as table %} {% import '@SyliusBootstrapAdminUi/shared/helper/pagination.html.twig' as pagination %} -{% set resources = hookable_metadata.context.resources %} -{% set data = resources.data %} -{% set definition = resources.definition %} +{% set resources = hookable_metadata.context.resources|default(this.resources|default(null)) %} +{% set data = resources.data|default([]) %} +{% set definition = resources.definition|default(null) %} +{% set is_live = attributes is defined %} {% if data|length > 0 %} -
+
{% if data|length > 0 and definition.actionGroups.bulk is defined and definition.getEnabledActions('bulk')|length > 0 %} diff --git a/src/BootstrapAdminUi/templates/shared/grid/action/show.html.twig b/src/BootstrapAdminUi/templates/shared/grid/action/show.html.twig index 9abbc6da..c7ab7c28 100644 --- a/src/BootstrapAdminUi/templates/shared/grid/action/show.html.twig +++ b/src/BootstrapAdminUi/templates/shared/grid/action/show.html.twig @@ -1,4 +1,6 @@ -{% set path = options.link.url|default(path(options.link.route|default(grid.requestConfiguration.getRouteName('show')), options.link.parameters|default({'id': data.id}))) %} +{% set default_route = grid.requestConfiguration is defined ? grid.requestConfiguration.getRouteName('show') : null %} + +{% set path = options.link.url|default(path(options.link.route|default(default_route)), options.link.parameters|default({'id': data.id})) %} {% set message = action.label|default('sylius.ui.show') %} diff --git a/src/BootstrapAdminUi/templates/shared/grid/action/update.html.twig b/src/BootstrapAdminUi/templates/shared/grid/action/update.html.twig index ef5cef26..3bcfdb35 100644 --- a/src/BootstrapAdminUi/templates/shared/grid/action/update.html.twig +++ b/src/BootstrapAdminUi/templates/shared/grid/action/update.html.twig @@ -1,4 +1,6 @@ -{% set path = options.link.url|default(path(options.link.route|default(grid.requestConfiguration.getRouteName('update')), options.link.parameters|default({'id': data.id}))) %} +{% set default_route = grid.requestConfiguration is defined ? grid.requestConfiguration.getRouteName('update') : null %} + +{% set path = options.link.url|default(path(options.link.route|default(default_route), options.link.parameters|default({'id': data.id}))) %} {% set message = action.label|default('sylius.ui.edit') %} diff --git a/src/BootstrapAdminUi/templates/shared/helper/pagination.html.twig b/src/BootstrapAdminUi/templates/shared/helper/pagination.html.twig index ade4ba99..64288b0c 100644 --- a/src/BootstrapAdminUi/templates/shared/helper/pagination.html.twig +++ b/src/BootstrapAdminUi/templates/shared/helper/pagination.html.twig @@ -21,7 +21,12 @@
diff --git a/src/BootstrapAdminUi/templates/shared/helper/sorting.html.twig b/src/BootstrapAdminUi/templates/shared/helper/sorting.html.twig index 16c18625..348c704c 100644 --- a/src/BootstrapAdminUi/templates/shared/helper/sorting.html.twig +++ b/src/BootstrapAdminUi/templates/shared/helper/sorting.html.twig @@ -2,8 +2,16 @@ {% set sorting_order = grid.getSortingOrder(field.name)|default('asc') %} {% if grid.isSortedBy(field.name) %} + {% set new_sorting_order = sorting_order == 'desc' ? 'asc' : 'desc' %} - + {{ field.label|trans }} {% if sorting_order == 'desc' %} @@ -19,7 +27,14 @@ {% else %} - + {{ field.label|trans }} diff --git a/src/BootstrapAdminUi/templates/shared/pagerfanta.html.twig b/src/BootstrapAdminUi/templates/shared/pagerfanta.html.twig index fc9800e1..799143b0 100644 --- a/src/BootstrapAdminUi/templates/shared/pagerfanta.html.twig +++ b/src/BootstrapAdminUi/templates/shared/pagerfanta.html.twig @@ -7,7 +7,17 @@ {%- endblock pager_widget -%} {%- block page_link -%} -
  • {{- page -}}
  • +
  • + + {{- page -}} + +
  • {%- endblock page_link -%} {%- block current_page_link -%} @@ -16,7 +26,12 @@ {%- block previous_page_link -%}
  • -
  • @@ -32,7 +47,13 @@ {%- block next_page_link -%}
  • -