├── Api
├── Data
│ ├── GridDataInterface.php
│ └── GridInterface.php
├── DataProcessorInterface.php
└── GridDataInterface.php
├── Block
├── GenericGrid.php
└── Grid
│ └── Filters.php
├── Controller
└── Adminhtml
│ └── Grid
│ ├── Data.php
│ ├── Filter.php
│ └── Index.php
├── Model
├── DataProcessor
│ ├── ChainProcessor.php
│ ├── DateProcessor.php
│ ├── DefaultProcessor.php
│ ├── IdProcessor.php
│ ├── ObfuscateProcessor.php
│ ├── PriceProcessor.php
│ └── StatusProcessor.php
├── Fields
│ ├── DataSourceInterface.php
│ └── DefaultDataSource.php
├── Grid.php
└── GridData.php
├── Plugin
├── Admin
│ └── SecretKeyValidator.php
└── WebapiAuthorizationPlugin.php
├── README.md
├── ViewModel
├── GenericViewModelGrid.php
└── Grid.php
├── composer.json
├── etc
├── acl.xml
├── adminhtml
│ ├── default.xml
│ ├── menu.xml
│ └── routes.xml
├── di.xml
├── module.xml
└── webapi.xml
├── registration.php
└── view
└── adminhtml
├── layout
├── adminhtml_dashboard_index.xml
├── default.xml
├── grid_grid_data.xml
├── grid_grid_index.xml
└── mage_grid_grid_index.xml
├── require-config.js
├── templates
├── grid
│ ├── additional-html.phtml
│ ├── filters.phtml
│ └── grid-component.phtml
└── gridjs.phtml
└── web
├── css
├── DataTable.css
└── gridjs.css
├── images
├── flag32.png
└── flags32.png
└── js
├── DataTable.js
├── gridjs.js
└── preact.js
/Api/Data/GridDataInterface.php:
--------------------------------------------------------------------------------
1 | labels
32 | */
33 | protected $fields;
34 |
35 | /**
36 | * @var string|null The database table name (if using SQL-based grid)
37 | */
38 | protected $tableName;
39 |
40 | /**
41 | * @var string|object|null The collection class (if using collection-based grid)
42 | */
43 | protected $collectionClass;
44 |
45 | /**
46 | * @var ManagerInterface For displaying admin error messages
47 | */
48 | protected $messageManager;
49 |
50 | /**
51 | * @var string The main grid template file
52 | */
53 | protected $_template = 'Mage_Grid::grid/grid-component.phtml';
54 |
55 | /**
56 | * @var array List of additional HTML/JS templates to render after the grid
57 | */
58 | protected $additionalHtmlTemplates = ['Mage_Grid::grid/additional-html.phtml'];
59 |
60 | /**
61 | * Constructor
62 | *
63 | * @param BackendContext $backendContext
64 | * @param GenericViewModelGrid $defaultViewModel
65 | * @param ManagerInterface $messageManager
66 | * @param array $data
67 | *
68 | * Initializes the grid block, sets up fields, data sources, and error handling.
69 | */
70 | public function __construct(
71 | BackendContext $backendContext,
72 | GenericViewModelGrid $defaultViewModel,
73 | ManagerInterface $messageManager,
74 | array $data = []
75 | ) {
76 | parent::__construct($backendContext, $data);
77 | $this->messageManager = $messageManager;
78 | // Use provided ViewModel or fallback to default
79 | $this->viewModel = $this->getData('viewModel') ?: $defaultViewModel;
80 | $dataProcessors = $this->getData('dataProcessors') ?: [];
81 | foreach ($dataProcessors as $field => $processor) {
82 | $this->viewModel->addDataProcessor($field, $processor);
83 | }
84 |
85 | // Set up fields and field names
86 | $this->fields = $this->getData('fields') ?: ['id' => 'ID'];
87 | $this->viewModel->setFields($this->fields);
88 | $this->viewModel->setFieldsNames($this->fields);
89 | $this->viewModel->setTableName($this->tableName);
90 | }
91 |
92 | /**
93 | * Get the ViewModel instance
94 | * @return GenericViewModelGrid
95 | */
96 | public function getViewModel()
97 | {
98 | return $this->viewModel;
99 | }
100 |
101 | /**
102 | * Get the configured fields (associative array)
103 | * @return array
104 | */
105 | public function getFields()
106 | {
107 | return $this->fields;
108 | }
109 |
110 | /**
111 | * Get the field keys (for use in data arrays)
112 | * @return array
113 | */
114 | public function getFieldsNames()
115 | {
116 | return $this->viewModel->getFieldsNames();
117 | }
118 |
119 | /**
120 | * Get the configured table name (if any)
121 | * @return string|null
122 | */
123 | public function getTableName()
124 | {
125 | return $this->tableName;
126 | }
127 |
128 | /**
129 | * Get the configured fields config
130 | * Must be called after getGridJsonData because it depends on the table name
131 | * @return array
132 | */
133 | public function getFieldsConfig()
134 | {
135 | $fieldsConfig = $this->viewModel->getFieldsConfig();
136 | $table = $this->viewModel->getCollection()->getMainTable();
137 |
138 | foreach ($fieldsConfig as $key => $value) {
139 | foreach ($value as $key2 => $value2) {
140 | if ($key2 === 'source_model' && is_object($value2)) {
141 | $value2->setTableName($table);
142 | $fieldsConfig[$key]['data'] = $value2->getValues($key);
143 | }
144 | }
145 | }
146 | return $fieldsConfig;
147 | }
148 |
149 | public function getFieldsHtml()
150 | {
151 | return $this->viewModel->getFieldsHtml();
152 | }
153 |
154 | /**
155 | * Get the processed filter fields
156 | * @param array $fields
157 | * @param array $fieldsConfig
158 | * @param array $filters
159 | * @return array
160 | */
161 | public function getProcessedFields($fields, $fieldsConfig, $filters)
162 | {
163 | foreach ($fields as $field) {
164 | $processedFields[$field] = [
165 | 'config' => isset($fieldsConfig[$field]) ? $fieldsConfig[$field] : [],
166 | 'hidden' => isset($fieldsConfig[$field]['hidden'])
167 | ? $fieldsConfig[$field]['hidden']
168 | : false,
169 | 'label' => isset($fieldsConfig[$field]['label'])
170 | ? $fieldsConfig[$field]['label']
171 | : ucwords(str_replace('_', ' ', $field)),
172 | 'element' => isset($fieldsConfig[$field]['element'])
173 | ? $fieldsConfig[$field]['element']
174 | : 'text',
175 | 'data' => isset($fieldsConfig[$field]['data'])
176 | ? $fieldsConfig[$field]['data']
177 | : [],
178 | 'filter_value' => isset($filters[$field])
179 | ? (is_array($filters[$field]) ? $filters[$field] : (string) $filters[$field])
180 | : (isset($fieldsConfig[$field]['element']) &&
181 | in_array($fieldsConfig[$field]['element'], ['select', 'multiselect']) ? [] : '')
182 | ];
183 | }
184 | return $processedFields;
185 | }
186 |
187 | /**
188 | * Lazy load the collection class
189 | */
190 | function lazyLoadCollectionClass()
191 | {
192 | $this->collectionClass = $this->getData('collectionClass');
193 |
194 | $this->tableName = $this->getData('tableName');
195 | // If a collection class is set, ignore table name
196 | if ($this->collectionClass !== 'none') {
197 | $this->tableName = null;
198 | }
199 | // Set collection class in ViewModel if provided
200 | if ($this->collectionClass !== 'none') {
201 | $this->viewModel->setCollectionClass($this->collectionClass);
202 | }
203 | // Require at least one data source
204 | if ($this->tableName === 'none' && $this->collectionClass === 'none') {
205 | $this->messageManager->addErrorMessage('Mage Grid: Collection class or table name is required');
206 | throw new \Exception('Mage Grid: Collection class or table name is required');
207 | }
208 | }
209 |
210 | /**
211 | * Get the grid data as JSON (for Grid.js)
212 | * @return string JSON-encoded data
213 | */
214 | public function getGridJsonData()
215 | {
216 | $this->lazyLoadCollectionClass();
217 |
218 | return $this->viewModel->getJsonGridData(array_keys($this->getFieldsNames()));
219 | }
220 |
221 | /**
222 | * Render all configured additional HTML/JS templates after the grid
223 | *
224 | * @return string Rendered HTML/JS (with error messages for missing templates)
225 | *
226 | * Usage: Place = $block->getAditionalHTML() ?> in your grid template.
227 | * Configure templates via layout XML with the 'additional_html_templates' argument.
228 | */
229 | public function getAditionalHTML()
230 | {
231 | $templates = $this->getData('additional_html_templates') ?: $this->additionalHtmlTemplates;
232 | if (!is_array($templates)) {
233 | $templates = [$templates];
234 | }
235 | $output = '';
236 | foreach ($templates as $template) {
237 | $templateFile = $this->getTemplateFile($template);
238 | if (!is_file($templateFile)) {
239 | $output .= '
Error: Additional HTML template not found: ' . htmlspecialchars($template) . '
';
240 | continue;
241 | }
242 | $output .= '' . $this->fetchView($templateFile);
243 | }
244 | return $output;
245 | }
246 |
247 | /**
248 | * Get filter value for a specific field
249 | *
250 | * @param string $field
251 | * @param array $filters
252 | * @return string|array
253 | */
254 | protected function getFilterValue($field, $filters)
255 | {
256 | $fieldsConfig = $this->getFieldsConfig();
257 |
258 | if (isset($fieldsConfig[$field]['element']) &&
259 | in_array($fieldsConfig[$field]['element'], ['select', 'multiselect'])) {
260 | // For select/multiselect, keep as array
261 | $filterValue = isset($filters[$field]) ? $filters[$field] : [];
262 | if (!is_array($filterValue)) {
263 | $filterValue = $filterValue ? [$filterValue] : [];
264 | }
265 | } else {
266 | // For text inputs, ensure string
267 | $filterValue = isset($filters[$field]) ? (string) $filters[$field] : '';
268 | }
269 |
270 | return $filterValue;
271 | }
272 |
273 | /**
274 | * Get current filter values
275 | *
276 | * @return array
277 | */
278 | public function getCurrentFilters()
279 | {
280 | return $this->getRequest()->getParam('filter', []);
281 | }
282 |
283 | /**
284 | * Get rendered filters HTML
285 | *
286 | * @return string
287 | */
288 | public function getFiltersHtml($filterData): string
289 | {
290 | return $this
291 | ->getLayout()
292 | ->createBlock(\Mage\Grid\Block\Grid\Filters::class)
293 | ->setFilterData($filterData)
294 | ->toHtml();
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/Block/Grid/Filters.php:
--------------------------------------------------------------------------------
1 | filterData = $data;
27 | return $this;
28 | }
29 |
30 | /**
31 | * Get filter data
32 | *
33 | * @return array
34 | */
35 | public function getFilterData(): array
36 | {
37 | return $this->filterData;
38 | }
39 |
40 | /**
41 | * Get fields data
42 | *
43 | * @return array
44 | */
45 | public function getFields(): array
46 | {
47 | return $this->filterData['fields'] ?? [];
48 | }
49 |
50 | /**
51 | * Get filters data
52 | *
53 | * @return array
54 | */
55 | public function getFilters(): array
56 | {
57 | return $this->filterData['filters'] ?? [];
58 | }
59 | }
--------------------------------------------------------------------------------
/Controller/Adminhtml/Grid/Data.php:
--------------------------------------------------------------------------------
1 | gridViewModel = $gridViewModel;
47 | $this->layoutFactory = $layoutFactory;
48 | $this->resultPageFactory = $resultPageFactory;
49 | }
50 |
51 | /**
52 | * Check admin permissions
53 | *
54 | * @return bool
55 | */
56 | protected function _isAllowed()
57 | {
58 | return $this->_authorization->isAllowed('Magento_Sales::sales');
59 | }
60 |
61 | /**
62 | * Get grid data via API
63 | *
64 | * @return ResponseInterface
65 | */
66 | public function execute()
67 | {
68 | try {
69 | // Get the layout and block
70 | $layout = $this->resultPageFactory->create()->getLayout();
71 | $request = $this->getRequest();
72 | $request->setParams(['data' => 'false']);
73 |
74 | $layout->getUpdate()->load(); // collect all handles (default, route, etc.)
75 | $layout->generateXml(); // merge the XML
76 | $layout->generateElements(); // turn tags into PHP objects
77 |
78 | $this->gridBlock = $layout->getBlock('grid_generic_grid');
79 |
80 | if (!$this->gridBlock) {
81 | throw new LocalizedException(__('Grid block not found'));
82 | }
83 |
84 | $fields = array_keys($this->gridBlock->getFieldsNames()); // array: ['id', 'order_number', ...]
85 | $fieldsFull = $this->gridBlock->getFields(); // associative array: ['id' => 'ID', ...]
86 | $jsonGridData = $this->gridBlock->getGridJsonData(); // JSON-encoded grid data
87 | // dd($jsonGridData);
88 |
89 | // Return JSON response
90 | $this->getResponse()->representJson(
91 | $jsonGridData
92 | );
93 | } catch (LocalizedException $e) {
94 | throw new \Exception('Error: ' . $e->getMessage(), 400);
95 | } catch (\Exception $e) {
96 | throw new \Exception('An error occurred while fetching grid data:' . $e->getMessage(), 500);
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Grid/Filter.php:
--------------------------------------------------------------------------------
1 | resultJsonFactory = $resultJsonFactory;
24 | $this->om = $om;
25 | }
26 |
27 | protected function _isAllowed()
28 | {
29 | return true; // Or set proper ACL permission
30 | }
31 |
32 | public function execute(): ResultInterface
33 | {
34 | // dd($this->getRequest()->getParams());
35 | try {
36 | $query = $this->getRequest()->getParam('query');
37 | if (!$query) {
38 | throw new \Exception(__('No query provided'));
39 | }
40 |
41 | try {
42 | $this->chatQuery = $this->om->get(ChatQuery::class);
43 | } catch (\Exception $e) {
44 | $this->logger->error('Error getting Chat AI extension: ' . $e->getMessage());
45 | return $this->resultJsonFactory->create()->setData([
46 | 'success' => false,
47 | 'message' => __('An error occurred while processing your request. Please try again.')
48 | ]);
49 | }
50 |
51 | // Prepare context for grid filtering
52 | $storeContext = [
53 | 'DataAPIUrl' => $this->getRequest()->getParam('DataUrl', ''),
54 | 'store_id' => $this->getRequest()->getParam('store', 0),
55 | 'website_id' => $this->getRequest()->getParam('website', 0)
56 | ];
57 |
58 | // Get AI response
59 | $aiResponse = $this->getAiResponse($query, $storeContext);
60 |
61 | $errorMessage = '';
62 | if (isset($aiResponse['success']) && $aiResponse['success'] === false) {
63 | if (isset($aiResponse['message'])) {
64 | $errorMessage = $aiResponse['message'];
65 | }
66 | throw new \Exception($errorMessage);
67 | }
68 |
69 |
70 | $groupByFields = json_decode($aiResponse, true)['groupByFields'];
71 | $urlParams = json_decode($aiResponse, true)['filterUrl'];
72 | $explanation = json_decode($aiResponse, true)['explanation'];
73 | $confidence = json_decode($aiResponse, true)['confidence'];
74 |
75 | // Parse AI response to get filter suggestions
76 | $filters = $this->parseAiResponse($aiResponse);
77 |
78 | return $this->resultJsonFactory->create()->setData([
79 | 'success' => true,
80 | 'suggestion' => [
81 | 'filterUrl' => $urlParams ?? '',
82 | 'groupByFields' => $groupByFields ?? '',
83 | 'explanation' => $explanation ?? '',
84 | 'confidence' => $confidence ?? '',
85 | 'field' => $filters[0]['field'] ?? 'entity_id',
86 | 'value' => $filters[0]['value'] ?? $query,
87 | 'filters' => $filters
88 | ],
89 | 'raw' => $aiResponse
90 | ]);
91 | } catch (\Exception $e) {
92 | return $this->resultJsonFactory->create()->setData([
93 | 'success' => false,
94 | 'message' => $e->getMessage()
95 | ]);
96 | }
97 | }
98 |
99 | /**
100 | * Get AI response for a query
101 | */
102 | private function getAiResponse($query, $storeContext)
103 | {
104 | try {
105 | // Get available grid fields and their types
106 | $gridFields = $this->getGridFields();
107 |
108 | // Get current filters from URL
109 | $currentFilters = $this->getCurrentFilters();
110 |
111 | // Get pagination info
112 | $pagination = $this->getPaginationInfo();
113 |
114 | // Get sorting info
115 | $sorting = $this->getSortingInfo();
116 |
117 | // Build enhanced system prompt
118 | $systemPrompt = 'You are a Magento Grid Filter Assistant. '
119 | . 'Your task is to analyze user queries and suggest appropriate filters for the grid.'
120 | . "\n\nRules:\n1. Only suggest filters that exist in the grid you you don't know ask for more context\n"
121 | . "2. Consider current filters and pagination\n"
122 | . "3. Provide multiple filter options when appropriate\n"
123 | . "4. If unsure, suggest multiple ask more context from user\n"
124 | . "5. Always respond in JSON format or ask more context from user\n"
125 | . "6. Respect field examples provided\n"
126 | . "7. Available Fields:\n" . json_encode($gridFields, JSON_PRETTY_PRINT) . "\n\nCurrent Context:\n" . json_encode([
127 | 'current_filters' => [],
128 | 'pagination' => $pagination,
129 | 'sorting' => $sorting,
130 | 'store_context' => $storeContext
131 | ], JSON_PRETTY_PRINT)
132 | . "\n\n8. Make sure you are having valid url for filterUrl \n\n"
133 | . "8.1 add new filters to the end of the url get parameters \n\n"
134 | . "8.2 add groupByFields if such is needed and allowed by the field config 'groupByField' => true, to the end of the url get parameters groupByFields=field1,field2 \n\n"
135 | . "9. make sure us are using correct domain and url path from the DatAPIurl . Thanks \n\n"
136 | . "9.1 url like key/gdgdgf/filter[entity_id]=value?page=1&pageSize=40 is not valid \n\n"
137 | . "9.2 url like key/gdgdgf/?filter[entity_id]=value&page=1&pageSize=40 is valid \n\n"
138 | . "9.3 if you have multiple filters like key/gdgdgf/?filter[entity_id]=value&filter[entity_id]=value2 make them like filter[entity_id][]=value \n\n"
139 | . "\n\nResponse Format:\n{\n \"filterUrl\": url with all filter parameters,\n \"groupByFields\": \"value\", \"filters\": [\n {\n \"field\": \"field_name\",\n \"value\": \"filter_value\",\n \"description\": \"Optional description of the filter\"\n }\n ],\n \"explanation\": \"Optional explanation of the suggested filters\",\n \"confidence\": \"high|medium|low\",\n \"alternative_suggestions\": [\n {\n \"filters\": [...],\n \"reason\": \"Why this alternative might be useful\"\n }\n ]\n}\n";
140 |
141 | // Use AgentoAI Chat Query to get response
142 | $response = $this->chatQuery->getAiResponse(
143 | $query,
144 | $storeContext,
145 | [], // No conversation history needed for grid filtering
146 | $systemPrompt
147 | );
148 |
149 | return $response;
150 | } catch (\Exception $e) {
151 | throw new \Exception(__('Error processing AI request: %1', $e->getMessage()));
152 | }
153 | }
154 |
155 | /**
156 | * Get available grid fields and their types
157 | */
158 | private function getGridFields()
159 | {
160 | $fields = [
161 | 'entity_id' => [
162 | 'type' => 'number',
163 | 'label' => 'Order ID',
164 | 'multiple' => false,
165 | 'alias' => 'entity id, order id , id',
166 | 'filter_example_get_url_parameter' => 'filter[entity_id][]=value',
167 | 'description' => 'Unique identifier for the record'
168 | ],
169 | 'increment_id' => [
170 | 'type' => 'string',
171 | 'label' => 'Increment ID',
172 | 'multiple' => false,
173 | 'filter_example_get_url_parameter' => 'filter[increment_id][]=value',
174 | 'description' => 'Order number or unique identifier'
175 | ],
176 | 'status' => [
177 | 'type' => 'select',
178 | 'label' => 'Status',
179 | 'multiple' => true,
180 | 'filter_example_get_url_parameter' => 'filter[status][]=value',
181 | 'options' => [
182 | 'pending' => 'Pending',
183 | 'processing' => 'Processing',
184 | 'complete' => 'Complete',
185 | 'canceled' => 'Canceled',
186 | 'holded' => 'Holded',
187 | 'rejected' => 'Rejected'
188 | ],
189 | 'description' => 'Current status of the record'
190 | ],
191 | 'created_at' => [
192 | 'multiple' => false,
193 | 'type' => 'date',
194 | 'label' => 'Created At',
195 | 'filter_example_get_url_parameter' => 'filter[created_at]=value',
196 | 'description' => 'Date when the record was created'
197 | ],
198 | 'grand_total' => [
199 | 'multiple' => false,
200 | 'type' => 'number',
201 | 'label' => 'Grand Total',
202 | 'alias' => 'grand_total, amount, sum, total',
203 | 'filter_example_get_url_parameter' => 'filter[grand_total]=value',
204 | 'description' => 'Total amount of the order'
205 | ],
206 | 'customer_email' => [
207 | 'groupByField' => true,
208 | 'multiple' => false,
209 | 'type' => 'string',
210 | 'label' => 'Customer Email',
211 | 'alias' => 'customer_email, email, user email, @email, @',
212 | 'filter_example_get_url_parameter' => 'filter[customer_email][]=value',
213 | 'description' => 'Customer email address'
214 | ],
215 | 'updated_at' => [
216 | 'multiple' => false,
217 | 'type' => 'date',
218 | 'label' => 'Updated At',
219 | 'filter_example_get_url_parameter' => 'filter[updated_at]=value',
220 | 'description' => 'Date when the record was last updated'
221 | ]
222 | ];
223 |
224 | return $fields;
225 | }
226 |
227 | /**
228 | * Get current filters from URL
229 | */
230 | private function getCurrentFilters()
231 | {
232 | $filters = [];
233 | $urlParams = $this->getRequest()->getParams();
234 | unset($urlParams['query']);
235 | foreach ($urlParams as $key => $value) {
236 | $field = str_replace('filter[', '', $key);
237 | $field = str_replace(']', '', $field);
238 | $filters[$field] = $value;
239 | }
240 |
241 | return $filters;
242 | }
243 |
244 | /**
245 | * Get pagination info
246 | */
247 | private function getPaginationInfo()
248 | {
249 | return [
250 | 'current_page' => $this->getRequest()->getParam('page', 1),
251 | 'page_size' => $this->getRequest()->getParam('pageSize', 20),
252 | 'total_records' => $this->getRequest()->getParam('totalRecords', 0)
253 | ];
254 | }
255 |
256 | /**
257 | * Get sorting info
258 | */
259 | private function getSortingInfo()
260 | {
261 | return [
262 | 'sort_field' => $this->getRequest()->getParam('sortField', null),
263 | 'sort_order' => $this->getRequest()->getParam('sortOrder', null)
264 | ];
265 | }
266 |
267 | /**
268 | * Parse AI response to get filter suggestions
269 | */
270 | private function parseAiResponse($response)
271 | {
272 | try {
273 | $data = json_decode($response, true);
274 | if (isset($data['filters']) && is_array($data['filters'])) {
275 | return $data['filters'];
276 | }
277 | throw new \Exception(__('Invalid AI response format'));
278 | } catch (\Exception $e) {
279 | // If parsing fails, create a basic filter
280 | return [
281 | [
282 | 'filterUrl' => '',
283 | 'field' => 'entity_id',
284 | 'operator' => 'like',
285 | 'value' => '%' . $response . '%'
286 | ]
287 | ];
288 | }
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/Controller/Adminhtml/Grid/Index.php:
--------------------------------------------------------------------------------
1 | resultPageFactory = $resultPageFactory;
17 | }
18 |
19 | /**
20 | * Check admin permissions
21 | *
22 | * @return bool
23 | */
24 | protected function _isAllowed()
25 | {
26 | return $this->_authorization->isAllowed('Magento_Sales::sales');
27 | }
28 |
29 | public function execute()
30 | {
31 | $resultPage = $this->resultPageFactory->create();
32 | $resultPage->setActiveMenu('Mage_Grid::grid');
33 | $resultPage->getConfig()->getTitle()->prepend(__('Orders'));
34 | return $resultPage;
35 | }
36 | }
--------------------------------------------------------------------------------
/Model/DataProcessor/ChainProcessor.php:
--------------------------------------------------------------------------------
1 | addProcessor($processor);
17 | }
18 | }
19 |
20 | /**
21 | * Add a processor to the chain
22 | *
23 | * @param DataProcessorInterface $processor
24 | * @return $this
25 | */
26 | public function addProcessor(DataProcessorInterface $processor)
27 | {
28 | $this->processors[] = $processor;
29 | return $this;
30 | }
31 |
32 | /**
33 | * @inheritDoc
34 | */
35 | public function process(string $field, $value, array $row = []): string
36 | {
37 | if (empty($this->processors)) {
38 | return $value;
39 | }
40 | usort($this->processors, function ($a, $b) {
41 | return $a->getOrder() <=> $b->getOrder();
42 | });
43 |
44 | $result = $value;
45 | foreach ($this->processors as $processor) {
46 | $result = $processor->process($field, $result, $row);
47 | }
48 | return $result;
49 | }
50 |
51 | public function getOrder(): int
52 | {
53 | return 0;
54 | }
55 | }
--------------------------------------------------------------------------------
/Model/DataProcessor/DateProcessor.php:
--------------------------------------------------------------------------------
1 | order = $order ?? 10;
13 | }
14 | /**
15 | * @inheritDoc
16 | */
17 | public function process(string $field, $value, array $row = []): string
18 | {
19 | if (empty($value)) {
20 | return '';
21 | }
22 |
23 | try {
24 | $date = new \DateTime($value);
25 | return $date->format('Y-m-d H:i:s');
26 | } catch (\Exception $e) {
27 | return $value;
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/Model/DataProcessor/DefaultProcessor.php:
--------------------------------------------------------------------------------
1 | order = $order ?? $this->order;
14 | }
15 |
16 | /**
17 | * @inheritDoc
18 | */
19 | public function process(string $field, $value, array $row = []): string
20 | {
21 | return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8');
22 | }
23 |
24 | public function getOrder(): int
25 | {
26 | return $this->order;
27 | }
28 | }
--------------------------------------------------------------------------------
/Model/DataProcessor/IdProcessor.php:
--------------------------------------------------------------------------------
1 | order);
16 | }
17 |
18 | /**
19 | * @inheritDoc
20 | */
21 | public function process(string $field, $value, array $row = []): string
22 | {
23 | if (!is_numeric($value)) {
24 | return $value;
25 | }
26 |
27 | // Get the order URL using the increment ID
28 | $orderUrl = $this->urlBuilder->getUrl(
29 | 'sales/order/view',
30 | ['order_id' => $value]
31 | );
32 |
33 | return sprintf(
34 | ' %s',
35 | $orderUrl,
36 | $value
37 | );
38 | }
39 | }
--------------------------------------------------------------------------------
/Model/DataProcessor/ObfuscateProcessor.php:
--------------------------------------------------------------------------------
1 | $%s',
21 | number_format((float)$value, 2)
22 | );
23 | }
24 | }
--------------------------------------------------------------------------------
/Model/DataProcessor/StatusProcessor.php:
--------------------------------------------------------------------------------
1 |
42 | %s
43 | %s
44 | ',
45 | $color,
46 | $color,
47 | $color,
48 | $rowJson,
49 | $icon,
50 | htmlspecialchars($value, ENT_QUOTES, 'UTF-8')
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Model/Fields/DataSourceInterface.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
43 | $this->cache = $cache;
44 | }
45 |
46 | public function setFields(array|string $fields)
47 | {
48 | if (is_string($fields)) {
49 | $this->fields[] = $fields;
50 | } else {
51 | $this->fields = $fields;
52 | }
53 | }
54 |
55 | /**
56 | * Set the table name
57 | * @param string $tableName
58 | */
59 | public function setTableName($tableName)
60 | {
61 | $this->tableName = $tableName;
62 | }
63 |
64 | /**
65 | * Get cache key for field values
66 | *
67 | * @param string $field
68 | * @return string
69 | */
70 | protected function getCacheKey($field)
71 | {
72 | return 'grid_field_values_' . $this->tableName . '_' . $field;
73 | }
74 |
75 | /**
76 | * Get all distinct values for a given field
77 | *
78 | * @param string $field
79 | * @return array
80 | */
81 | public function getValues($field)
82 | {
83 | $cacheKey = $this->getCacheKey($field);
84 | $cachedData = $this->cache->load($cacheKey);
85 |
86 | if ($cachedData !== false) {
87 | return json_decode($cachedData, true);
88 | }
89 |
90 | $connection = $this->resource->getConnection();
91 | $select = $connection->select()
92 | ->from($this->tableName, [$field])
93 | ->distinct();
94 | $results = $connection->fetchCol($select);
95 |
96 | // Cache the results
97 | $this->cache->save(
98 | json_encode($results),
99 | $cacheKey,
100 | [self::CACHE_TAG],
101 | self::CACHE_LIFETIME
102 | );
103 |
104 | return $results;
105 | }
106 |
107 | /**
108 | * Get all distinct values for multiple fields
109 | *
110 | * @param array $fields
111 | * @return array
112 | */
113 | public function getAllValues(array $fields)
114 | {
115 | $fields = array_merge($this->fields, $fields);
116 | $result = [];
117 |
118 | foreach ($fields as $field) {
119 | $result[$field] = $this->getValues($field);
120 | }
121 |
122 | return $result;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Model/Grid.php:
--------------------------------------------------------------------------------
1 | getData('data') ?? [];
17 | }
18 |
19 | /**
20 | * @inheritDoc
21 | */
22 | public function setGridData(array $data): self
23 | {
24 | return $this->setData('data', $data);
25 | }
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | public function getData($key = '', $index = null): array
31 | {
32 | return $this->getGridData($key, $index);
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function getTotalCount(): int
39 | {
40 | return (int)($this->getData('total_count') ?? 0);
41 | }
42 |
43 | /**
44 | * @inheritDoc
45 | */
46 | public function setTotalCount(int $totalCount): self
47 | {
48 | return $this->setData('total_count', $totalCount);
49 | }
50 |
51 | /**
52 | * @inheritDoc
53 | */
54 | public function getConfig(): array
55 | {
56 | return $this->getData('config') ?? [];
57 | }
58 |
59 | /**
60 | * @inheritDoc
61 | */
62 | public function setConfig(array $config): self
63 | {
64 | return $this->setData('config', $config);
65 | }
66 |
67 | /**
68 | * @inheritDoc
69 | */
70 | public function getFields(): array
71 | {
72 | return $this->getData('fields') ?? [];
73 | }
74 |
75 | /**
76 | * @inheritDoc
77 | */
78 | public function setFields(array $fields): self
79 | {
80 | return $this->setData('fields', $fields);
81 | }
82 |
83 | /**
84 | * @inheritDoc
85 | */
86 | public function getFilters(): array
87 | {
88 | return $this->getData('filters') ?? [];
89 | }
90 |
91 | /**
92 | * @inheritDoc
93 | */
94 | public function setFilters(array $filters): self
95 | {
96 | return $this->setData('filters', $filters);
97 | }
98 |
99 | /**
100 | * @inheritDoc
101 | */
102 | public function getPerformanceMetrics(): array
103 | {
104 | return $this->getData('performance_metrics') ?? [];
105 | }
106 |
107 | /**
108 | * @inheritDoc
109 | */
110 | public function setPerformanceMetrics(array $metrics): self
111 | {
112 | return $this->setData('performance_metrics', $metrics);
113 | }
114 |
115 | /**
116 | * Get grid data
117 | *
118 | * @param string $gridId
119 | * @param array $filters
120 | * @param int $page
121 | * @param int $pageSize
122 | * @return array
123 | */
124 | public function getGrid(
125 | string $gridId,
126 | array $filters = [],
127 | int $page = 1,
128 | int $pageSize = 20
129 | ): array {
130 | // Example data structure
131 | return [
132 | 'data' => $this->getData('data') ?? [],
133 | 'total' => $this->getData('total') ?? 0,
134 | 'page' => $page,
135 | 'pageSize' => $pageSize
136 | ];
137 | }
138 | }
--------------------------------------------------------------------------------
/Model/GridData.php:
--------------------------------------------------------------------------------
1 | viewModel = $viewModel;
25 | }
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | public function getGridData(
31 | string $gridId,
32 | array $filters = [],
33 | int $page = 1,
34 | int $pageSize = 20
35 | ): array {
36 | try {
37 | // Start timing
38 | $startTime = microtime(true);
39 |
40 | // Get grid data
41 | $data = $this->viewModel->getGridData($filters, $page, $pageSize);
42 | $totalCount = $this->viewModel->getTotalCount();
43 |
44 | // Calculate performance metrics
45 | $endTime = microtime(true);
46 | $executionTime = ($endTime - $startTime) * 1000; // Convert to milliseconds
47 |
48 | // Return response data
49 | return [
50 | 'data' => $data,
51 | 'total' => $totalCount,
52 | 'page' => $page,
53 | 'pageSize' => $pageSize,
54 | 'performance' => [
55 | 'execution_time' => $executionTime,
56 | 'sql_time' => $this->viewModel->getSqlTime(),
57 | 'count_time' => $this->viewModel->getCountTime()
58 | ]
59 | ];
60 | } catch (\Exception $e) {
61 | throw new LocalizedException(__('Error retrieving grid data: %1', $e->getMessage()));
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/Plugin/Admin/SecretKeyValidator.php:
--------------------------------------------------------------------------------
1 | request = $request;
24 | }
25 |
26 | /**
27 | * Modify useSecretKey to return false for our specific action
28 | */
29 | public function aroundUseSecretKey(
30 | Url $subject,
31 | \Closure $proceed
32 | ): bool {
33 | if (strpos($this->request->getUri()->getPath(), 'grid/data') ||
34 | strpos($this->request->getUri()->getPath(), 'grid/filter')) {
35 | return false; // disable secret key for this action
36 | }
37 |
38 | // For all other actions, use the default behavior
39 | return $proceed();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Plugin/WebapiAuthorizationPlugin.php:
--------------------------------------------------------------------------------
1 | adminSession = $adminSession;
31 | $this->request = $request;
32 | }
33 |
34 | /**
35 | * Allow access if user has admin session only for grid API
36 | *
37 | * @param Authorization $subject
38 | * @param bool $result
39 | * @return bool
40 | */
41 | public function afterIsAllowed(Authorization $subject, bool $result): bool
42 | {
43 | // If already authorized, return true
44 | if ($result) {
45 | return true;
46 | }
47 |
48 | // Check if this is our grid API route
49 | $path = $this->request->getPathInfo();
50 | if (strpos($path, '/V1/grid/') !== 0) {
51 | return $result;
52 | }
53 |
54 | // For grid API, check if user has admin session
55 | return $this->adminSession->isLoggedIn();
56 | }
57 | }
--------------------------------------------------------------------------------
/ViewModel/GenericViewModelGrid.php:
--------------------------------------------------------------------------------
1 | DataProcessorInterface mapping
68 | */
69 | protected $dataProcessors = [];
70 |
71 | /**
72 | * @var DefaultProcessor The default processor for fields
73 | */
74 | protected $defaultProcessor;
75 |
76 | /**
77 | * @var ChainProcessor The chain processor for layered processing
78 | */
79 | protected $chainProcessor;
80 |
81 | /**
82 | * @var string|null The SQL table name (if using SQL mode)
83 | */
84 | public $tableName;
85 |
86 | /**
87 | * @var string|null The raw SQL query (if using SQL mode)
88 | */
89 | public $sqlQuery = null;
90 |
91 | /**
92 | * @var string|null The SQL query for total count (if using SQL mode)
93 | */
94 | public $totalSqlQuery;
95 |
96 | /**
97 | * @var array List of field keys for the grid
98 | */
99 | public $fields = [];
100 |
101 | /**
102 | * @var array List of field keys for the grid that should be rendered as HTML
103 | */
104 | public $fieldsHtml = [];
105 |
106 | /**
107 | * @var array List of field config for the grid
108 | *
109 | * example: [
110 | * "customer_group_id" => [
111 | * "label" => "Customer Group ID",
112 | * "element" => "select",
113 | * "source_model" => "Mage\Grid\Model\Fields\DefaultDataSource"
114 | * ]
115 | * ]
116 | */
117 | public $fieldsConfig = [];
118 |
119 | /**
120 | * @var array List of field labels for the grid
121 | */
122 | public $fieldsNames = [];
123 |
124 | /**
125 | * @var array Current filter values
126 | */
127 | public $filters = [];
128 |
129 | /**
130 | * @var array Fields to group by
131 | */
132 | protected $groupByFields = [];
133 |
134 | /**
135 | * @var string|object|null The collection class (if using collection mode)
136 | */
137 | public string|object|null $collectionClass = null;
138 |
139 | /**
140 | * @var int The maximum number of rows to return (for SQL mode)
141 | */
142 | public $LIMIT = 1000;
143 |
144 | /**
145 | * @var int The default page size for pagination
146 | */
147 | public $DEFAULT_LIMIT = 40;
148 |
149 | /**
150 | * @var string|null The raw SQL (if set directly)
151 | */
152 | protected $sql;
153 |
154 | /**
155 | * @var LayoutInterface For rendering templates (if needed)
156 | */
157 | protected $layout;
158 |
159 | /**
160 | * @var ResourceConnection
161 | */
162 | protected $resource;
163 |
164 | /**
165 | * @var bool Whether to use AJAX for data loading
166 | */
167 | public $isAjax = true;
168 |
169 | /**
170 | * @var CacheInterface
171 | */
172 | private $cache;
173 |
174 | /**
175 | * Constructor
176 | *
177 | * @param ObjectManagerInterface $objectManager
178 | * @param RequestInterface $request
179 | * @param LoggerInterface $logger
180 | * @param LayoutInterface $layout
181 | * @param DefaultProcessor $defaultProcessor
182 | * @param ChainProcessor $chainProcessor
183 | * @param ResourceConnection $resource
184 | * @param CacheInterface $cache
185 | * @param array $dataProcessors
186 | *
187 | * All dependencies are injected for maximum flexibility and testability.
188 | */
189 | public function __construct(
190 | ObjectManagerInterface $objectManager,
191 | RequestInterface $request,
192 | LoggerInterface $logger,
193 | LayoutInterface $layout,
194 | DefaultProcessor $defaultProcessor,
195 | ChainProcessor $chainProcessor,
196 | ResourceConnection $resource,
197 | CacheInterface $cache,
198 | array $dataProcessors = []
199 | ) {
200 | $this->objectManager = $objectManager;
201 | $this->request = $request;
202 | $this->logger = $logger;
203 | $this->layout = $layout;
204 | $this->defaultProcessor = $defaultProcessor;
205 | $this->chainProcessor = $chainProcessor;
206 | $this->resource = $resource;
207 | $this->cache = $cache;
208 | $this->dataProcessors = $dataProcessors;
209 | }
210 |
211 | /**
212 | * Set the collection class (for collection-based grids)
213 | * @param string|object $collectionClass
214 | * @return $this
215 | */
216 | public function setCollectionClass($collectionClass)
217 | {
218 | $this->collectionClass = $collectionClass;
219 | return $this;
220 | }
221 |
222 | /**
223 | * Build and return the SQL query for SQL-based grids
224 | * @param array $fields
225 | * @param array $filters
226 | * @return string|false
227 | */
228 | public function getSqlQuery($fields, $filters)
229 | {
230 | if ($this->getTableName() && $this->sqlQuery === null && $this->collectionClass === null) {
231 | $sql = 'SELECT ' . implode(', ', $fields) . ' FROM ' . $this->getTableName() . ' WHERE 1=1 ';
232 | foreach ($filters as $field => $value) {
233 | $sql .= ' AND ' . $field . ' LIKE :' . $field;
234 | }
235 | $this->sqlQuery = $sql;
236 | return $this->sqlQuery;
237 | } else {
238 | return false;
239 | }
240 | }
241 |
242 | /**
243 | * Get the data for the grid (from collection or SQL)
244 | * Handles filtering, pagination, and sorting.
245 | *
246 | * @param array $fields
247 | * @param array $filters
248 | * @return array
249 | */
250 | public function getData(array $fields = [], array $filters = [])
251 | {
252 | try {
253 | // If using SQL mode, execute the SQL query
254 | if ($this->getSqlQuery($fields, $filters)) {
255 | return $this->executeSqlQuery();
256 | }
257 |
258 | /* @var $collection AbstractCollection */
259 | $collection = $this->getCollection();
260 |
261 | $fields = array_merge($fields, $this->getFields());
262 | //dd($fields);
263 | $this->setFields($fields);
264 |
265 | // Select fields dynamically
266 | //$collection->addFieldToSelect($fields);
267 |
268 | $this->setGroupByFields(explode(',', $this->request->getParam('groupByFields', '')));
269 |
270 | // Handle filters
271 | // $filters = $this->getFilters();
272 |
273 | // Build the select fields with appropriate aggregation
274 | $selectFields = [];
275 | foreach ($fields as $field) {
276 | if (in_array($field, $this->groupByFields)) {
277 | // If it's a group by field, use it as is
278 | $selectFields[$field] = $field;
279 | } else {
280 | // For all other fields, use SUM by default when grouping
281 | if (!empty($this->groupByFields) && $this->strposx($field, ["total", "amount", "price", "qty", "count"]) !== false) {
282 | $selectFields[$field] = sprintf('SUM(%s) as %s', $field, $field);
283 | } elseif (!empty($this->groupByFields) && $this->strposx($field, ["entity_id"]) !== false) {
284 | //dd($field);
285 | $selectFields[$field] = sprintf('COUNT(%s) as entity_id', $field, $field);
286 | }
287 | else {
288 | // If not grouping, use the field as is
289 | $selectFields[$field] = $field;
290 | }
291 | }
292 | }
293 |
294 | // Select fields dynamically
295 | $collection->getSelect()->columns($selectFields);
296 |
297 | if(count($filters) === 0) $filters = $this->getFilters();
298 | //dd($filters);
299 |
300 | foreach ($filters as $field => $value) {
301 | if (in_array($field, $fields)) {
302 | if (is_array($value)) {
303 | $collection->addFieldToFilter($field, ['in' => $value]);
304 | } else {
305 | if (isset($this->fieldsConfig[$field]['element']) && $this->fieldsConfig[$field]['element'] === 'select') {
306 | $collection->addFieldToFilter($field, ['in' => $value]);
307 | } else {
308 | $collection->addFieldToFilter($field, ['like' => $value . '%']);
309 | }
310 | }
311 | }
312 | }
313 |
314 | // Add GROUP BY clause if needed
315 | if (!empty($this->groupByFields)) {
316 | $collection->getSelect()->group($this->groupByFields);
317 | }
318 |
319 | // Handle pagination
320 | $page = (int) $this->request->getParam('page', 1);
321 | $limit = (int) $this->request->getParam('pageSize', $this->DEFAULT_LIMIT);
322 |
323 | // $page = $offset / $limit + 1;
324 | $collection->setPageSize($limit);
325 | $collection->setCurPage($page);
326 |
327 | // Handle sorting
328 | $sortField = $this->request->getParam('sortField', null);
329 | $sortOrder = $this->request->getParam('sortOrder', null);
330 | if ($sortField && $sortOrder) {
331 | $collection->setOrder($sortField, $sortOrder);
332 | }
333 |
334 | // Log the SQL query (uncomment for debugging)
335 | //dd('SQL Query: ' . $collection->getSelect()->__toString());
336 |
337 | return $collection->getData();
338 | } catch (\Exception $e) {
339 | $this->logger->error('Error fetching data: ' . $e->getMessage());
340 | return [
341 | 'error' => true,
342 | 'message' => __('Error fetching data: %1', $e->getMessage())
343 | ];
344 | }
345 | }
346 |
347 | /**
348 | * Find the position of any needle in a haystack
349 | *
350 | * @param string $haystack The string to search in
351 | * @param array|string $needles The needle(s) to search for
352 | * @return int|false Returns the position of the first needle found, or false if none found
353 | */
354 | protected function strposx($haystack, $needles)
355 | {
356 | if (!is_array($needles)) {
357 | return strpos($haystack, $needles);
358 | }
359 |
360 | foreach ($needles as $needle) {
361 | $pos = strpos($haystack, $needle);
362 | if ($pos !== false) {
363 | return true;
364 | }
365 | }
366 |
367 | return false;
368 | }
369 |
370 |
371 | /**
372 | * Get the grid data as JSON (for AJAX/Grid.js)
373 | *
374 | * @param array $fields
375 | * @param array $filters
376 | * @return string JSON-encoded data
377 | */
378 | public function getJsonGridData(array $fields = [], array $filters = [], bool $return = true)
379 | {
380 | $result = [];
381 |
382 | $filters = array_merge($this->request->getParam('filter', []), $filters);
383 | $this->setFilters($filters);
384 |
385 | // If AJAX request, output JSON and exit
386 | if (strtolower($this->request->getParam('data', 'false')) === 'true') {
387 | try {
388 | $result = $this->getGridData($fields, $filters);
389 |
390 | header('Content-Type: application/json');
391 | echo json_encode($result);
392 | exit;
393 | } catch (\Throwable $e) {
394 | header('Content-Type: application/json');
395 | echo json_encode(['error' => true, 'message' => $e->getMessage()]);
396 | exit;
397 | }
398 | }
399 | if ($return) {
400 | $result = $this->getGridData($fields, $filters);
401 | }
402 |
403 | return json_encode($result);
404 | }
405 |
406 | /**
407 | * Get the grid data as an array (with processors applied)
408 | *
409 | * @param array $fields
410 | * @param array $filters
411 | * @return array
412 | */
413 | public function getGridData(array $fields = [], array $filters = [])
414 | {
415 | $timeStart = microtime(true);
416 | $data = $this->getData($fields);
417 | if (isset($data['error'])) {
418 | return json_encode(['error' => true, 'message' => $data['message']]);
419 | }
420 | $timeEnd = microtime(true);
421 | $sqlTime = $timeEnd - $timeStart;
422 | // $item is the row data
423 | $jsonData = array_map(function ($item) use ($fields) {
424 | return array_map(function ($field) use ($item) {
425 | $value = $item[$field] ?? '';
426 | $processor = $this->getDataProcessor($field);
427 | if ($processor) {
428 | $value = $processor->process($field, $value, $item);
429 | }
430 | // Always run the chain processor last
431 | $value = $this->chainProcessor->process($field, $value, $item);
432 | return $value;
433 | }, $fields);
434 | }, $data);
435 |
436 | $timeStart = microtime(true);
437 | $totalCount = $this->getTotalCount();
438 | $timeEnd = microtime(true);
439 | $timeCount = $timeEnd - $timeStart;
440 | $result = ['data' => $jsonData, 'total' => $totalCount, 'timeCount' => $timeCount, 'timeSql' => $sqlTime];
441 | return $result;
442 | }
443 |
444 | /**
445 | * Set the fields for the grid coming from the block .xml configuration
446 | * @param array $fields
447 | * @return $this example [
448 | * "order_id" => "order_id",
449 | * "customer_group_id" => "customer_group_id",
450 | * "customer_email" => "customer_email",
451 | * "created_at" => "created_at",
452 | * "grand_total" => "grand_total",
453 | * "status" => "status",
454 | * "payment_method" => "payment_method"
455 | * ]
456 | */
457 | function setFields(array $fields)
458 | {
459 | foreach ($fields as $key => $field) {
460 | // dump($field);
461 | if (is_string($field)) {
462 | $this->fields[$key] = $key;
463 | } else if (is_array($field)) {
464 | foreach ($field as $key2 => $value) {
465 | if ($key2 === 'source_model') {
466 | $this->fields[$key] = $key;
467 | try {
468 | $this->fieldsConfig[$key][$key2] = $this->objectManager->create($value);
469 | } catch (\Exception $e) {
470 | $this->fieldsConfig[$key][$key2] = $e->getMessage();
471 | $this->logger->error('Error creating data source model: ' . $e->getMessage());
472 | }
473 | } else {
474 | $this->fields[$key] = $key;
475 | $this->fieldsConfig[$key][$key2] = $value;
476 | }
477 | if ($key2 === 'html') {
478 | $this->fieldsHtml[] = $key;
479 | }
480 | }
481 | }
482 | }
483 | // dd($this->fieldsConfig);
484 | return $this;
485 | }
486 |
487 | /**
488 | * Set fields to group by checking if they exist in the fields array
489 | *
490 | * @param array $fields Array of field names to group by
491 | * @return $this
492 | */
493 | public function setGroupByFields(array $fields)
494 | {
495 | foreach ($fields as $field) {
496 | if (in_array($field, $this->fields)) {
497 | $this->groupByFields[] = $field;
498 | }
499 | }
500 |
501 | return $this;
502 | }
503 |
504 | /**
505 | * Get the fields that should be rendered as HTML
506 | * @return array
507 | */
508 | public function getFieldsHtml()
509 | {
510 | return $this->fieldsHtml;
511 | }
512 |
513 | /**
514 | * Set the raw SQL query (for SQL mode)
515 | * @param string|null $sql
516 | * @return $this
517 | */
518 | function setSqlQuery($sql = null)
519 | {
520 | $this->sqlQuery = $sql;
521 | return $this;
522 | }
523 |
524 | /**
525 | * Execute the raw SQL query (for SQL mode)
526 | * @return array|false
527 | */
528 | function executeSqlQuery()
529 | {
530 | if (!$this->sqlQuery) {
531 | return false;
532 | }
533 | $connection = $this->getCollection()->getConnection();
534 | return $connection->fetchAll($this->sqlQuery);
535 | }
536 |
537 | /**
538 | * Set the table name (for SQL mode)
539 | * @param string|null $tableName
540 | * @return $this
541 | */
542 | function setTableName(null|string $tableName)
543 | {
544 | $this->tableName = $tableName;
545 | return $this;
546 | }
547 |
548 | /**
549 | * Get the table name (for SQL mode)
550 | * @return string|null
551 | */
552 | function getTableName()
553 | {
554 | return $this->tableName;
555 | }
556 |
557 | /**
558 | * Get the configured fields
559 | * @return array
560 | */
561 | function getFields()
562 | {
563 | return $this->fields;
564 | }
565 |
566 | function getFieldsConfig()
567 | {
568 | return $this->fieldsConfig;
569 | }
570 |
571 | /**
572 | * Set the field labels (for display)
573 | * @param array $fields
574 | * @return $this
575 | */
576 | function setFieldsNames(array $fields)
577 | {
578 | foreach ($fields as $key => $field) {
579 | if (is_string($field)) {
580 | $this->fieldsNames[$key] = $field;
581 | } else if (is_array($field)) {
582 | $this->fieldsNames[$key] = $field['label'];
583 | }
584 | }
585 | return $this;
586 | }
587 |
588 | /**
589 | * Get the field labels (for display)
590 | * @return array
591 | */
592 | function getFieldsNames()
593 | {
594 | return $this->fieldsNames;
595 | }
596 |
597 | /**
598 | * Set the filters for the grid
599 | * @param array $filters
600 | * @return $this
601 | */
602 | function setFilters(array $filters)
603 | {
604 | $this->filters = $filters;
605 | return $this;
606 | }
607 |
608 | /**
609 | * Get the current filters (merged from request and internal state)
610 | * @return array
611 | */
612 | function getFilters()
613 | {
614 | // Handle filters from request and internal state
615 | $filters = $this->request->getParam('filter', []);
616 | $filters = array_merge($filters, $this->filters);
617 | $this->setFilters($filters);
618 | return $this->filters;
619 | }
620 |
621 | /**
622 | * Get the collection instance (collection mode)
623 | * @return AbstractCollection|null
624 | */
625 | public function getCollection()
626 | {
627 | if (!$this->collection) {
628 | try {
629 | if ($this->collectionClass !== null) {
630 | if (is_string($this->collectionClass)) {
631 | $this->collection = $this->objectManager->create($this->collectionClass);
632 | } else if (is_object($this->collectionClass)) {
633 | $this->collection = $this->collectionClass;
634 | } else {
635 | throw new \Exception('Grid Collection class issue: collectionClass parameter is not a string or object');
636 | }
637 | } else {
638 | throw new \Exception('Grid Collection class not set: collectionClass parameter is required');
639 | }
640 | $this->setTableName($this->collection->getMainTable());
641 | } catch (\Exception $e) {
642 | $this->logger->error('Error creating collection: ' . $e->getMessage());
643 | }
644 | }
645 | return $this->collection;
646 | }
647 |
648 | /**
649 | * Get cache key for total count
650 | *
651 | * @param string $tableName
652 | * @param array $filters
653 | * @return string
654 | */
655 | protected function getTotalCountCacheKey($tableName, $filters = [])
656 | {
657 | $filterKey = empty($filters) ? 'no_filters' : md5(json_encode($filters));
658 | return 'grid_total_count_' . $tableName . '_' . $filterKey;
659 | }
660 |
661 | /**
662 | * Get the total count of records (for pagination)
663 | *
664 | * @param string|null $tableName
665 | * @return int
666 | */
667 | public function getTotalCount($tableName = null)
668 | {
669 | if ($this->sqlQuery) {
670 | return $this->executeSqlQuery();
671 | }
672 |
673 | try {
674 | // Get the database connection
675 | $connection = $this->resource->getConnection();
676 | $tableName = $tableName ?: $this->tableName;
677 |
678 | // Build the query
679 | $select = $connection->select()->from($tableName, 'COUNT(*)');
680 |
681 | // Apply filters if they exist
682 | if (!empty($this->filters)) {
683 | foreach ($this->filters as $field => $value) {
684 | if (is_array($value)) {
685 | $select->where($field . ' IN (?)', $value);
686 | } else {
687 | $select->where($field . ' LIKE ?', $value . '%');
688 | }
689 | }
690 | }
691 |
692 | // Add GROUP BY clause if needed
693 | if (!empty($this->groupByFields)) {
694 | $select->group($this->groupByFields);
695 | }
696 |
697 | // Check cache first
698 | $cacheKey = $this->getTotalCountCacheKey($tableName, $this->filters);
699 | $cachedCount = $this->cache->load($cacheKey);
700 |
701 | if ($cachedCount !== false) {
702 | $count = (int) $cachedCount;
703 | // If cached count is > 100, use it
704 | if ($count > 100) {
705 | return $count;
706 | }
707 | }
708 |
709 | // Hard limit to optimise huge table performace
710 | $select->limit(999);
711 |
712 | // Execute the query
713 | $count = (int) $connection->fetchOne($select);
714 |
715 | // Cache only if count is greater than 100 to improve performance
716 | if ($count > 100) {
717 | $this->cache->save(
718 | (string) $count,
719 | $cacheKey,
720 | [self::CACHE_TAG],
721 | self::CACHE_LIFETIME
722 | );
723 | }
724 | } catch (\Exception $e) {
725 | $this->logger->error('Error getting total count: ' . $e->getMessage());
726 | throw $e;
727 | }
728 |
729 | return $count;
730 | }
731 |
732 | /**
733 | * Get a request parameter (helper)
734 | * @param string $param
735 | * @param string $default
736 | * @return string
737 | */
738 | public function getRequestParam($param, $default = '')
739 | {
740 | return $this->request->getParam($param, $default);
741 | }
742 |
743 | /**
744 | * Render the grid as HTML (using the main template)
745 | * @param string $blockName
746 | * @return string
747 | */
748 | public function getGridHtml()
749 | {
750 | $fields = $this->getFields();
751 | $jsonGridData = $this->getJsonGridData($fields);
752 |
753 | if (isset($jsonGridData['error']) && $jsonGridData['error']) {
754 | return '' . $jsonGridData['message'] . '
';
755 | }
756 |
757 | $gridHtml = $this
758 | ->layout
759 | ->createBlock('Magento\Framework\View\Element\Template')
760 | ->setTemplate('Mage_Grid::grid/grid-component.phtml')
761 | ->setData('jsonGridData', $jsonGridData)
762 | ->setData('fields', $fields)
763 | ->toHtml();
764 |
765 | return $gridHtml;
766 | }
767 |
768 | /**
769 | * Add a data processor for a specific field (for custom formatting)
770 | * @param string $field
771 | * @param DataProcessorInterface $processor
772 | * @return $this
773 | */
774 | public function addDataProcessor(string $field, DataProcessorInterface $processor)
775 | {
776 | $this->dataProcessors[$field] = $processor;
777 | return $this;
778 | }
779 |
780 | /**
781 | * Get the data processor for a specific field (if any)
782 | * @param string $field
783 | * @return DataProcessorInterface|null
784 | */
785 | protected function getDataProcessor(string $field): DataProcessorInterface|null
786 | {
787 | // Get the processor for the field, or default
788 | $processor = $this->dataProcessors[$field] ?? null;
789 |
790 | return $processor;
791 | }
792 | }
793 |
--------------------------------------------------------------------------------
/ViewModel/Grid.php:
--------------------------------------------------------------------------------
1 | grid = $grid;
23 | }
24 |
25 | /**
26 | * Get grid instance
27 | *
28 | * @return Grid
29 | */
30 | public function getGrid(): Grid
31 | {
32 | return $this->grid;
33 | }
34 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mage/grid",
3 | "description": "Magento 2 custom React GridJS integration ",
4 | "type": "magento2-module",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Yehor Shytikov",
9 | "email": "egorshitikov@gmail.com"
10 | }
11 | ],
12 | "minimum-stability": "dev",
13 | "autoload": {
14 | "psr-4": {
15 | "Mage\\Grid\\": ""
16 | },
17 | "files": [
18 | "registration.php"
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/etc/acl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/etc/adminhtml/default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | Mage\Grid\ViewModel\GenericViewModelGrid
9 | none
10 |
11 | - ID
12 |
13 |
14 | none
15 | 20
16 | true
17 |
18 | - Mage_Grid::grid/additional-html.phtml
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/etc/adminhtml/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
13 |
--------------------------------------------------------------------------------
/etc/adminhtml/routes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/etc/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Mage\Grid\ViewModel\GenericViewModelGrid
6 |
7 |
8 |
9 |
10 | Mage\Grid\Model\DataProcessor\DefaultProcessor
11 |
12 | - Mage\Grid\Model\DataProcessor\ObfuscateProcessor
13 | - Mage\Grid\Model\DataProcessor\IdProcessor
14 | - Mage\Grid\Model\DataProcessor\ObfuscateProcessor
15 | - Mage\Grid\Model\DataProcessor\DateProcessor
16 | - Mage\Grid\Model\DataProcessor\DateProcessor
17 | - Mage\Grid\Model\DataProcessor\PriceProcessor
18 | - Mage\Grid\Model\DataProcessor\PriceProcessor
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/etc/module.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/etc/webapi.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/view/adminhtml/layout/default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
12 |
13 | Mage\Grid\ViewModel\GenericViewModelGrid
14 | Mage_Grid::grid/additional-html.phtml
15 | none
16 |
17 | - ID
18 |
19 |
20 | none
21 | 20
22 | true
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/view/adminhtml/layout/grid_grid_data.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 | Mage\Grid\ViewModel\GenericViewModelGrid
10 | Magento\Sales\Model\ResourceModel\Order\Grid\Collection
11 |
12 | - ID
13 | - Increment ID
14 | -
15 |
- Status
16 | - select
17 | - true
18 | - Mage\Grid\Model\Fields\DefaultDataSource
19 |
20 | -
21 |
- Store
22 | - true
23 | - multiselect
24 | - Mage\Grid\Model\Fields\DefaultDataSource
25 |
26 | - Customer Email
27 | - Created At
28 | -
29 |
- Grand Total
30 | - true
31 |
32 | -
33 |
- Payment Method
34 | - multiselect
35 | - Mage\Grid\Model\Fields\DefaultDataSource
36 |
37 |
38 |
39 | - Mage\Grid\Model\DataProcessor\StatusProcessor
40 |
41 | 20
42 | true
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/view/adminhtml/layout/grid_grid_index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 | Mage\Grid\ViewModel\GenericViewModelGrid
10 | Magento\Sales\Model\ResourceModel\Order\Grid\Collection
11 |
12 | -
13 |
- ID
14 | - true
15 |
16 | - Increment ID
17 | -
18 |
- Status
19 | - select
20 | - true
21 | - Mage\Grid\Model\Fields\DefaultDataSource
22 |
23 | -
24 |
- Store
25 | - true
26 | - multiselect
27 | - Mage\Grid\Model\Fields\DefaultDataSource
28 |
29 | - Customer Email
30 | - Created At
31 | -
32 |
- Grand Total
33 | - true
34 |
35 | -
36 |
- Payment Method
37 | - multiselect
38 | - Mage\Grid\Model\Fields\DefaultDataSource
39 |
40 |
41 |
42 | - Mage\Grid\Model\DataProcessor\StatusProcessor
43 |
44 | 20
45 | true
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/view/adminhtml/layout/mage_grid_grid_index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Mage\Grid\ViewModel\GridViewModel
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/view/adminhtml/require-config.js:
--------------------------------------------------------------------------------
1 |
2 | var config = {
3 | paths: {
4 | gridjs: 'Mage_Gridjs/js/gridjs',
5 | DataTable: 'Mage_Gridjs/js/DataTable'
6 | //preact: 'Mage_Gridjs/js/preact',
7 | },
8 | shim: {
9 | slick: {
10 | deps: ['jquery', 'jquery/ui']
11 | }
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/view/adminhtml/templates/grid/additional-html.phtml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/view/adminhtml/templates/grid/grid-component.phtml:
--------------------------------------------------------------------------------
1 | getAditionalHTML())
12 | //
13 | // For full Grid.js documentation and advanced configuration, see:
14 | // https://gridjs.io/docs
15 |
16 | $fields = array_keys($block->getFieldsNames()); // array: ['id', 'order_number', ...]
17 | $fieldsFull = $block->getFields(); // associative array: ['id' => 'ID', ...]
18 | $block->lazyLoadCollectionClass();
19 | // $jsonGridData = $block->getGridJsonData(); // JSON-encoded grid data
20 | $fieldsConfig = $block->getFieldsConfig();
21 | $tableName = $block->getTableName();
22 | $fieldsNames = $block->getFieldsNames();
23 | $aditionalHTML = $block->getAditionalHTML();
24 | $fieldsHtml = $block->getFieldsHtml();
25 | // Get filter values from request
26 | $filters = $this->getRequest()->getParam('filter', []);
27 |
28 | // Process field configurations and filter values
29 | $processedFields = $block->getProcessedFields($fields, $fieldsConfig, $filters);
30 |
31 | $filterData = [
32 | 'fields' => $processedFields,
33 | 'filters' => $filters
34 | ];
35 | // Now start rendering the template
36 | ?>
37 |
38 |
39 | = $block->getFiltersHtml($filterData) ?>
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
72 |
73 |
307 |
308 |
345 |
346 |
347 | = $block->getAditionalHTML() ?>
348 |
--------------------------------------------------------------------------------
/view/adminhtml/templates/gridjs.phtml:
--------------------------------------------------------------------------------
1 |
4 |
5 | Adobe and Partners stop business with Russia and Belarus!
6 | Grid JS
7 |
8 |
15 |
16 |
43 |
44 |
45 | DataTable
46 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/view/adminhtml/web/css/DataTable.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 | :root {
3 | --dt-row-selected: 13, 110, 253;
4 | --dt-row-selected-text: 255, 255, 255;
5 | --dt-row-selected-link: 9, 10, 11;
6 | --dt-row-stripe: 0, 0, 0;
7 | --dt-row-hover: 0, 0, 0;
8 | --dt-column-ordering: 0, 0, 0;
9 | --dt-html-background: white;
10 | }
11 | :root.dark {
12 | --dt-html-background: rgb(33, 37, 41);
13 | }
14 |
15 | table.dataTable td.dt-control {
16 | text-align: center;
17 | cursor: pointer;
18 | }
19 | table.dataTable td.dt-control:before {
20 | display: inline-block;
21 | box-sizing: border-box;
22 | content: "";
23 | border-top: 5px solid transparent;
24 | border-left: 10px solid rgba(0, 0, 0, 0.5);
25 | border-bottom: 5px solid transparent;
26 | border-right: 0px solid transparent;
27 | }
28 | table.dataTable tr.dt-hasChild td.dt-control:before {
29 | border-top: 10px solid rgba(0, 0, 0, 0.5);
30 | border-left: 5px solid transparent;
31 | border-bottom: 0px solid transparent;
32 | border-right: 5px solid transparent;
33 | }
34 |
35 | html.dark table.dataTable td.dt-control:before,
36 | :root[data-bs-theme=dark] table.dataTable td.dt-control:before {
37 | border-left-color: rgba(255, 255, 255, 0.5);
38 | }
39 | html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
40 | :root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
41 | border-top-color: rgba(255, 255, 255, 0.5);
42 | border-left-color: transparent;
43 | }
44 |
45 | div.dt-scroll-body thead tr,
46 | div.dt-scroll-body tfoot tr {
47 | height: 0;
48 | }
49 | div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td,
50 | div.dt-scroll-body tfoot tr th,
51 | div.dt-scroll-body tfoot tr td {
52 | height: 0 !important;
53 | padding-top: 0px !important;
54 | padding-bottom: 0px !important;
55 | border-top-width: 0px !important;
56 | border-bottom-width: 0px !important;
57 | }
58 | div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing,
59 | div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,
60 | div.dt-scroll-body tfoot tr td div.dt-scroll-sizing {
61 | height: 0 !important;
62 | overflow: hidden !important;
63 | }
64 |
65 | table.dataTable thead > tr > th:active,
66 | table.dataTable thead > tr > td:active {
67 | outline: none;
68 | }
69 | table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before,
70 | table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
71 | table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before {
72 | position: absolute;
73 | display: block;
74 | bottom: 50%;
75 | content: "▲";
76 | content: "▲"/"";
77 | }
78 | table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
79 | table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
80 | table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
81 | position: absolute;
82 | display: block;
83 | top: 50%;
84 | content: "▼";
85 | content: "▼"/"";
86 | }
87 | table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc,
88 | table.dataTable thead > tr > td.dt-orderable-asc,
89 | table.dataTable thead > tr > td.dt-orderable-desc,
90 | table.dataTable thead > tr > td.dt-ordering-asc,
91 | table.dataTable thead > tr > td.dt-ordering-desc {
92 | position: relative;
93 | padding-right: 30px;
94 | }
95 | table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order,
96 | table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order,
97 | table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order,
98 | table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order,
99 | table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
100 | position: absolute;
101 | right: 12px;
102 | top: 0;
103 | bottom: 0;
104 | width: 12px;
105 | }
106 | table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
107 | table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
108 | table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after,
109 | table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before,
110 | table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
111 | table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
112 | table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after,
113 | table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before,
114 | table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
115 | left: 0;
116 | opacity: 0.125;
117 | line-height: 9px;
118 | font-size: 0.8em;
119 | }
120 | table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc,
121 | table.dataTable thead > tr > td.dt-orderable-asc,
122 | table.dataTable thead > tr > td.dt-orderable-desc {
123 | cursor: pointer;
124 | }
125 | table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover,
126 | table.dataTable thead > tr > td.dt-orderable-asc:hover,
127 | table.dataTable thead > tr > td.dt-orderable-desc:hover {
128 | outline: 2px solid rgba(0, 0, 0, 0.05);
129 | outline-offset: -2px;
130 | }
131 | table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
132 | table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
133 | table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
134 | opacity: 0.6;
135 | }
136 | table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before,
137 | table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after,
138 | table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before {
139 | display: none;
140 | }
141 | table.dataTable thead > tr > th:active,
142 | table.dataTable thead > tr > td:active {
143 | outline: none;
144 | }
145 |
146 | div.dt-scroll-body > table.dataTable > thead > tr > th,
147 | div.dt-scroll-body > table.dataTable > thead > tr > td {
148 | overflow: hidden;
149 | }
150 |
151 | :root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover,
152 | :root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover,
153 | :root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover,
154 | :root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover,
155 | :root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover,
156 | :root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover,
157 | :root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover {
158 | outline: 2px solid rgba(255, 255, 255, 0.05);
159 | }
160 |
161 | div.dt-processing {
162 | position: absolute;
163 | top: 50%;
164 | left: 50%;
165 | width: 200px;
166 | margin-left: -100px;
167 | margin-top: -22px;
168 | text-align: center;
169 | padding: 2px;
170 | z-index: 10;
171 | }
172 | div.dt-processing > div:last-child {
173 | position: relative;
174 | width: 80px;
175 | height: 15px;
176 | margin: 1em auto;
177 | }
178 | div.dt-processing > div:last-child > div {
179 | position: absolute;
180 | top: 0;
181 | width: 13px;
182 | height: 13px;
183 | border-radius: 50%;
184 | background: rgb(13, 110, 253);
185 | background: rgb(var(--dt-row-selected));
186 | animation-timing-function: cubic-bezier(0, 1, 1, 0);
187 | }
188 | div.dt-processing > div:last-child > div:nth-child(1) {
189 | left: 8px;
190 | animation: datatables-loader-1 0.6s infinite;
191 | }
192 | div.dt-processing > div:last-child > div:nth-child(2) {
193 | left: 8px;
194 | animation: datatables-loader-2 0.6s infinite;
195 | }
196 | div.dt-processing > div:last-child > div:nth-child(3) {
197 | left: 32px;
198 | animation: datatables-loader-2 0.6s infinite;
199 | }
200 | div.dt-processing > div:last-child > div:nth-child(4) {
201 | left: 56px;
202 | animation: datatables-loader-3 0.6s infinite;
203 | }
204 |
205 | @keyframes datatables-loader-1 {
206 | 0% {
207 | transform: scale(0);
208 | }
209 | 100% {
210 | transform: scale(1);
211 | }
212 | }
213 | @keyframes datatables-loader-3 {
214 | 0% {
215 | transform: scale(1);
216 | }
217 | 100% {
218 | transform: scale(0);
219 | }
220 | }
221 | @keyframes datatables-loader-2 {
222 | 0% {
223 | transform: translate(0, 0);
224 | }
225 | 100% {
226 | transform: translate(24px, 0);
227 | }
228 | }
229 | table.dataTable.nowrap th, table.dataTable.nowrap td {
230 | white-space: nowrap;
231 | }
232 | table.dataTable th,
233 | table.dataTable td {
234 | box-sizing: border-box;
235 | }
236 | table.dataTable th.dt-left,
237 | table.dataTable td.dt-left {
238 | text-align: left;
239 | }
240 | table.dataTable th.dt-center,
241 | table.dataTable td.dt-center {
242 | text-align: center;
243 | }
244 | table.dataTable th.dt-right,
245 | table.dataTable td.dt-right {
246 | text-align: right;
247 | }
248 | table.dataTable th.dt-justify,
249 | table.dataTable td.dt-justify {
250 | text-align: justify;
251 | }
252 | table.dataTable th.dt-nowrap,
253 | table.dataTable td.dt-nowrap {
254 | white-space: nowrap;
255 | }
256 | table.dataTable th.dt-empty,
257 | table.dataTable td.dt-empty {
258 | text-align: center;
259 | vertical-align: top;
260 | }
261 | table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date,
262 | table.dataTable td.dt-type-numeric,
263 | table.dataTable td.dt-type-date {
264 | text-align: right;
265 | }
266 | table.dataTable thead th,
267 | table.dataTable thead td,
268 | table.dataTable tfoot th,
269 | table.dataTable tfoot td {
270 | text-align: left;
271 | }
272 | table.dataTable thead th.dt-head-left,
273 | table.dataTable thead td.dt-head-left,
274 | table.dataTable tfoot th.dt-head-left,
275 | table.dataTable tfoot td.dt-head-left {
276 | text-align: left;
277 | }
278 | table.dataTable thead th.dt-head-center,
279 | table.dataTable thead td.dt-head-center,
280 | table.dataTable tfoot th.dt-head-center,
281 | table.dataTable tfoot td.dt-head-center {
282 | text-align: center;
283 | }
284 | table.dataTable thead th.dt-head-right,
285 | table.dataTable thead td.dt-head-right,
286 | table.dataTable tfoot th.dt-head-right,
287 | table.dataTable tfoot td.dt-head-right {
288 | text-align: right;
289 | }
290 | table.dataTable thead th.dt-head-justify,
291 | table.dataTable thead td.dt-head-justify,
292 | table.dataTable tfoot th.dt-head-justify,
293 | table.dataTable tfoot td.dt-head-justify {
294 | text-align: justify;
295 | }
296 | table.dataTable thead th.dt-head-nowrap,
297 | table.dataTable thead td.dt-head-nowrap,
298 | table.dataTable tfoot th.dt-head-nowrap,
299 | table.dataTable tfoot td.dt-head-nowrap {
300 | white-space: nowrap;
301 | }
302 | table.dataTable tbody th.dt-body-left,
303 | table.dataTable tbody td.dt-body-left {
304 | text-align: left;
305 | }
306 | table.dataTable tbody th.dt-body-center,
307 | table.dataTable tbody td.dt-body-center {
308 | text-align: center;
309 | }
310 | table.dataTable tbody th.dt-body-right,
311 | table.dataTable tbody td.dt-body-right {
312 | text-align: right;
313 | }
314 | table.dataTable tbody th.dt-body-justify,
315 | table.dataTable tbody td.dt-body-justify {
316 | text-align: justify;
317 | }
318 | table.dataTable tbody th.dt-body-nowrap,
319 | table.dataTable tbody td.dt-body-nowrap {
320 | white-space: nowrap;
321 | }
322 |
323 | /*
324 | * Table styles
325 | */
326 | table.dataTable {
327 | width: 100%;
328 | margin: 0 auto;
329 | border-spacing: 0;
330 | /*
331 | * Header and footer styles
332 | */
333 | /*
334 | * Body styles
335 | */
336 | }
337 | table.dataTable thead th,
338 | table.dataTable tfoot th {
339 | font-weight: bold;
340 | }
341 | table.dataTable > thead > tr > th,
342 | table.dataTable > thead > tr > td {
343 | padding: 10px;
344 | border-bottom: 1px solid rgba(0, 0, 0, 0.3);
345 | }
346 | table.dataTable > thead > tr > th:active,
347 | table.dataTable > thead > tr > td:active {
348 | outline: none;
349 | }
350 | table.dataTable > tfoot > tr > th,
351 | table.dataTable > tfoot > tr > td {
352 | border-top: 1px solid rgba(0, 0, 0, 0.3);
353 | padding: 10px 10px 6px 10px;
354 | }
355 | table.dataTable > tbody > tr {
356 | background-color: transparent;
357 | }
358 | table.dataTable > tbody > tr:first-child > * {
359 | border-top: none;
360 | }
361 | table.dataTable > tbody > tr:last-child > * {
362 | border-bottom: none;
363 | }
364 | table.dataTable > tbody > tr.selected > * {
365 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.9);
366 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.9);
367 | color: rgb(255, 255, 255);
368 | color: rgb(var(--dt-row-selected-text));
369 | }
370 | table.dataTable > tbody > tr.selected a {
371 | color: rgb(9, 10, 11);
372 | color: rgb(var(--dt-row-selected-link));
373 | }
374 | table.dataTable > tbody > tr > th,
375 | table.dataTable > tbody > tr > td {
376 | padding: 8px 10px;
377 | }
378 | table.dataTable.row-border > tbody > tr > *, table.dataTable.display > tbody > tr > * {
379 | border-top: 1px solid rgba(0, 0, 0, 0.15);
380 | }
381 | table.dataTable.row-border > tbody > tr:first-child > *, table.dataTable.display > tbody > tr:first-child > * {
382 | border-top: none;
383 | }
384 | table.dataTable.row-border > tbody > tr.selected + tr.selected > td, table.dataTable.display > tbody > tr.selected + tr.selected > td {
385 | border-top-color: rgba(13, 110, 253, 0.65);
386 | border-top-color: rgba(var(--dt-row-selected), 0.65);
387 | }
388 | table.dataTable.cell-border > tbody > tr > * {
389 | border-top: 1px solid rgba(0, 0, 0, 0.15);
390 | border-right: 1px solid rgba(0, 0, 0, 0.15);
391 | }
392 | table.dataTable.cell-border > tbody > tr > *:first-child {
393 | border-left: 1px solid rgba(0, 0, 0, 0.15);
394 | }
395 | table.dataTable.cell-border > tbody > tr:first-child > * {
396 | border-top: 1px solid rgba(0, 0, 0, 0.3);
397 | }
398 | table.dataTable.stripe > tbody > tr:nth-child(odd) > *, table.dataTable.display > tbody > tr:nth-child(odd) > * {
399 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.023);
400 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.023);
401 | }
402 | table.dataTable.stripe > tbody > tr:nth-child(odd).selected > *, table.dataTable.display > tbody > tr:nth-child(odd).selected > * {
403 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.923);
404 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.923);
405 | }
406 | table.dataTable.hover > tbody > tr:hover > *, table.dataTable.display > tbody > tr:hover > * {
407 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.035);
408 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.035);
409 | }
410 | table.dataTable.hover > tbody > tr.selected:hover > *, table.dataTable.display > tbody > tr.selected:hover > * {
411 | box-shadow: inset 0 0 0 9999px #0d6efd !important;
412 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 1) !important;
413 | }
414 | table.dataTable.order-column > tbody tr > .sorting_1,
415 | table.dataTable.order-column > tbody tr > .sorting_2,
416 | table.dataTable.order-column > tbody tr > .sorting_3, table.dataTable.display > tbody tr > .sorting_1,
417 | table.dataTable.display > tbody tr > .sorting_2,
418 | table.dataTable.display > tbody tr > .sorting_3 {
419 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019);
420 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019);
421 | }
422 | table.dataTable.order-column > tbody tr.selected > .sorting_1,
423 | table.dataTable.order-column > tbody tr.selected > .sorting_2,
424 | table.dataTable.order-column > tbody tr.selected > .sorting_3, table.dataTable.display > tbody tr.selected > .sorting_1,
425 | table.dataTable.display > tbody tr.selected > .sorting_2,
426 | table.dataTable.display > tbody tr.selected > .sorting_3 {
427 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919);
428 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919);
429 | }
430 | table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_1, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_1 {
431 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.054);
432 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.054);
433 | }
434 | table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_2, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_2 {
435 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.047);
436 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.047);
437 | }
438 | table.dataTable.display > tbody > tr:nth-child(odd) > .sorting_3, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd) > .sorting_3 {
439 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.039);
440 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.039);
441 | }
442 | table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_1 {
443 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.954);
444 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.954);
445 | }
446 | table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_2 {
447 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.947);
448 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.947);
449 | }
450 | table.dataTable.display > tbody > tr:nth-child(odd).selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr:nth-child(odd).selected > .sorting_3 {
451 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.939);
452 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.939);
453 | }
454 | table.dataTable.display > tbody > tr.even > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_1 {
455 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.019);
456 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.019);
457 | }
458 | table.dataTable.display > tbody > tr.even > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_2 {
459 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.011);
460 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.011);
461 | }
462 | table.dataTable.display > tbody > tr.even > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even > .sorting_3 {
463 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.003);
464 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-column-ordering), 0.003);
465 | }
466 | table.dataTable.display > tbody > tr.even.selected > .sorting_1, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_1 {
467 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.919);
468 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.919);
469 | }
470 | table.dataTable.display > tbody > tr.even.selected > .sorting_2, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_2 {
471 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.911);
472 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.911);
473 | }
474 | table.dataTable.display > tbody > tr.even.selected > .sorting_3, table.dataTable.order-column.stripe > tbody > tr.even.selected > .sorting_3 {
475 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.903);
476 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.903);
477 | }
478 | table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 {
479 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.082);
480 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.082);
481 | }
482 | table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 {
483 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.074);
484 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.074);
485 | }
486 | table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 {
487 | box-shadow: inset 0 0 0 9999px rgba(0, 0, 0, 0.062);
488 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.062);
489 | }
490 | table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 {
491 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.982);
492 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.982);
493 | }
494 | table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 {
495 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.974);
496 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.974);
497 | }
498 | table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 {
499 | box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.962);
500 | box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.962);
501 | }
502 | table.dataTable.compact thead th,
503 | table.dataTable.compact thead td,
504 | table.dataTable.compact tfoot th,
505 | table.dataTable.compact tfoot td,
506 | table.dataTable.compact tbody th,
507 | table.dataTable.compact tbody td {
508 | padding: 4px;
509 | }
510 |
511 | /*
512 | * Control feature layout
513 | */
514 | div.dt-container {
515 | position: relative;
516 | clear: both;
517 | }
518 | div.dt-container div.dt-layout-row {
519 | display: table;
520 | clear: both;
521 | width: 100%;
522 | }
523 | div.dt-container div.dt-layout-row.dt-layout-table {
524 | display: block;
525 | }
526 | div.dt-container div.dt-layout-row.dt-layout-table div.dt-layout-cell {
527 | display: block;
528 | }
529 | div.dt-container div.dt-layout-cell {
530 | display: table-cell;
531 | vertical-align: middle;
532 | padding: 5px 0;
533 | }
534 | div.dt-container div.dt-layout-cell.dt-full {
535 | text-align: center;
536 | }
537 | div.dt-container div.dt-layout-cell.dt-start {
538 | text-align: left;
539 | }
540 | div.dt-container div.dt-layout-cell.dt-end {
541 | text-align: right;
542 | }
543 | div.dt-container div.dt-layout-cell:empty {
544 | display: none;
545 | }
546 | div.dt-container .dt-search input {
547 | border: 1px solid #aaa;
548 | border-radius: 3px;
549 | padding: 5px;
550 | background-color: transparent;
551 | color: inherit;
552 | margin-left: 3px;
553 | }
554 | div.dt-container .dt-input {
555 | border: 1px solid #aaa;
556 | border-radius: 3px;
557 | padding: 5px;
558 | background-color: transparent;
559 | color: inherit;
560 | }
561 | div.dt-container select.dt-input {
562 | padding: 4px;
563 | }
564 | div.dt-container .dt-paging .dt-paging-button {
565 | box-sizing: border-box;
566 | display: inline-block;
567 | min-width: 1.5em;
568 | padding: 0.5em 1em;
569 | margin-left: 2px;
570 | text-align: center;
571 | text-decoration: none !important;
572 | cursor: pointer;
573 | color: inherit !important;
574 | border: 1px solid transparent;
575 | border-radius: 2px;
576 | background: transparent;
577 | }
578 | div.dt-container .dt-paging .dt-paging-button.current, div.dt-container .dt-paging .dt-paging-button.current:hover {
579 | color: inherit !important;
580 | border: 1px solid rgba(0, 0, 0, 0.3);
581 | background-color: rgba(0, 0, 0, 0.05);
582 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, rgba(230, 230, 230, 0.05)), color-stop(100%, rgba(0, 0, 0, 0.05))); /* Chrome,Safari4+ */
583 | background: -webkit-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* Chrome10+,Safari5.1+ */
584 | background: -moz-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* FF3.6+ */
585 | background: -ms-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* IE10+ */
586 | background: -o-linear-gradient(top, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* Opera 11.10+ */
587 | background: linear-gradient(to bottom, rgba(230, 230, 230, 0.05) 0%, rgba(0, 0, 0, 0.05) 100%); /* W3C */
588 | }
589 | div.dt-container .dt-paging .dt-paging-button.disabled, div.dt-container .dt-paging .dt-paging-button.disabled:hover, div.dt-container .dt-paging .dt-paging-button.disabled:active {
590 | cursor: default;
591 | color: rgba(0, 0, 0, 0.5) !important;
592 | border: 1px solid transparent;
593 | background: transparent;
594 | box-shadow: none;
595 | }
596 | div.dt-container .dt-paging .dt-paging-button:hover {
597 | color: white !important;
598 | border: 1px solid #111;
599 | background-color: #111;
600 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); /* Chrome,Safari4+ */
601 | background: -webkit-linear-gradient(top, #585858 0%, #111 100%); /* Chrome10+,Safari5.1+ */
602 | background: -moz-linear-gradient(top, #585858 0%, #111 100%); /* FF3.6+ */
603 | background: -ms-linear-gradient(top, #585858 0%, #111 100%); /* IE10+ */
604 | background: -o-linear-gradient(top, #585858 0%, #111 100%); /* Opera 11.10+ */
605 | background: linear-gradient(to bottom, #585858 0%, #111 100%); /* W3C */
606 | }
607 | div.dt-container .dt-paging .dt-paging-button:active {
608 | outline: none;
609 | background-color: #0c0c0c;
610 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); /* Chrome,Safari4+ */
611 | background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Chrome10+,Safari5.1+ */
612 | background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* FF3.6+ */
613 | background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* IE10+ */
614 | background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); /* Opera 11.10+ */
615 | background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); /* W3C */
616 | box-shadow: inset 0 0 3px #111;
617 | }
618 | div.dt-container .dt-paging .ellipsis {
619 | padding: 0 1em;
620 | }
621 | div.dt-container .dt-length,
622 | div.dt-container .dt-search,
623 | div.dt-container .dt-info,
624 | div.dt-container .dt-processing,
625 | div.dt-container .dt-paging {
626 | color: inherit;
627 | }
628 | div.dt-container .dataTables_scroll {
629 | clear: both;
630 | }
631 | div.dt-container .dataTables_scroll div.dt-scroll-body {
632 | -webkit-overflow-scrolling: touch;
633 | }
634 | div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > th, div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > td, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > th, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > td {
635 | vertical-align: middle;
636 | }
637 | div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > th > div.dataTables_sizing,
638 | div.dt-container .dataTables_scroll div.dt-scroll-body > table > thead > tr > td > div.dataTables_sizing, div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > th > div.dataTables_sizing,
639 | div.dt-container .dataTables_scroll div.dt-scroll-body > table > tbody > tr > td > div.dataTables_sizing {
640 | height: 0;
641 | overflow: hidden;
642 | margin: 0 !important;
643 | padding: 0 !important;
644 | }
645 | div.dt-container.dt-empty-footer tbody > tr:last-child > * {
646 | border-bottom: 1px solid rgba(0, 0, 0, 0.3);
647 | }
648 | div.dt-container.dt-empty-footer .dt-scroll-body {
649 | border-bottom: 1px solid rgba(0, 0, 0, 0.3);
650 | }
651 | div.dt-container.dt-empty-footer .dt-scroll-body tbody > tr:last-child > * {
652 | border-bottom: none;
653 | }
654 |
655 | @media screen and (max-width: 767px) {
656 | div.dt-container div.dt-layout-row {
657 | display: block;
658 | }
659 | div.dt-container div.dt-layout-cell {
660 | display: block;
661 | }
662 | div.dt-container div.dt-layout-cell.dt-full, div.dt-container div.dt-layout-cell.dt-start, div.dt-container div.dt-layout-cell.dt-end {
663 | text-align: center;
664 | }
665 | }
666 | @media screen and (max-width: 640px) {
667 | .dt-container .dt-length,
668 | .dt-container .dt-search {
669 | float: none;
670 | text-align: center;
671 | }
672 | .dt-container .dt-search {
673 | margin-top: 0.5em;
674 | }
675 | }
676 | html.dark {
677 | --dt-row-hover: 255, 255, 255;
678 | --dt-row-stripe: 255, 255, 255;
679 | --dt-column-ordering: 255, 255, 255;
680 | }
681 | html.dark table.dataTable > thead > tr > th,
682 | html.dark table.dataTable > thead > tr > td {
683 | border-bottom: 1px solid rgb(89, 91, 94);
684 | }
685 | html.dark table.dataTable > thead > tr > th:active,
686 | html.dark table.dataTable > thead > tr > td:active {
687 | outline: none;
688 | }
689 | html.dark table.dataTable > tfoot > tr > th,
690 | html.dark table.dataTable > tfoot > tr > td {
691 | border-top: 1px solid rgb(89, 91, 94);
692 | }
693 | html.dark table.dataTable.row-border > tbody > tr > *, html.dark table.dataTable.display > tbody > tr > * {
694 | border-top: 1px solid rgb(64, 67, 70);
695 | }
696 | html.dark table.dataTable.row-border > tbody > tr:first-child > *, html.dark table.dataTable.display > tbody > tr:first-child > * {
697 | border-top: none;
698 | }
699 | html.dark table.dataTable.row-border > tbody > tr.selected + tr.selected > td, html.dark table.dataTable.display > tbody > tr.selected + tr.selected > td {
700 | border-top-color: rgba(13, 110, 253, 0.65);
701 | border-top-color: rgba(var(--dt-row-selected), 0.65);
702 | }
703 | html.dark table.dataTable.cell-border > tbody > tr > th,
704 | html.dark table.dataTable.cell-border > tbody > tr > td {
705 | border-top: 1px solid rgb(64, 67, 70);
706 | border-right: 1px solid rgb(64, 67, 70);
707 | }
708 | html.dark table.dataTable.cell-border > tbody > tr > th:first-child,
709 | html.dark table.dataTable.cell-border > tbody > tr > td:first-child {
710 | border-left: 1px solid rgb(64, 67, 70);
711 | }
712 | html.dark .dt-container.dt-empty-footer table.dataTable {
713 | border-bottom: 1px solid rgb(89, 91, 94);
714 | }
715 | html.dark .dt-container .dt-search input,
716 | html.dark .dt-container .dt-length select {
717 | border: 1px solid rgba(255, 255, 255, 0.2);
718 | background-color: var(--dt-html-background);
719 | }
720 | html.dark .dt-container .dt-paging .dt-paging-button.current, html.dark .dt-container .dt-paging .dt-paging-button.current:hover {
721 | border: 1px solid rgb(89, 91, 94);
722 | background: rgba(255, 255, 255, 0.15);
723 | }
724 | html.dark .dt-container .dt-paging .dt-paging-button.disabled, html.dark .dt-container .dt-paging .dt-paging-button.disabled:hover, html.dark .dt-container .dt-paging .dt-paging-button.disabled:active {
725 | color: #666 !important;
726 | }
727 | html.dark .dt-container .dt-paging .dt-paging-button:hover {
728 | border: 1px solid rgb(53, 53, 53);
729 | background: rgb(53, 53, 53);
730 | }
731 | html.dark .dt-container .dt-paging .dt-paging-button:active {
732 | background: #3a3a3a;
733 | }
734 |
735 | /*
736 | * Overrides for RTL support
737 | */
738 | *[dir=rtl] table.dataTable thead th,
739 | *[dir=rtl] table.dataTable thead td,
740 | *[dir=rtl] table.dataTable tfoot th,
741 | *[dir=rtl] table.dataTable tfoot td {
742 | text-align: right;
743 | }
744 | *[dir=rtl] table.dataTable th.dt-type-numeric, *[dir=rtl] table.dataTable th.dt-type-date,
745 | *[dir=rtl] table.dataTable td.dt-type-numeric,
746 | *[dir=rtl] table.dataTable td.dt-type-date {
747 | text-align: left;
748 | }
749 | *[dir=rtl] div.dt-container div.dt-layout-cell.dt-start {
750 | text-align: right;
751 | }
752 | *[dir=rtl] div.dt-container div.dt-layout-cell.dt-end {
753 | text-align: left;
754 | }
755 | *[dir=rtl] div.dt-container div.dt-search input {
756 | margin: 0 3px 0 0;
757 | }
--------------------------------------------------------------------------------
/view/adminhtml/web/css/gridjs.css:
--------------------------------------------------------------------------------
1 | .gridjs-footer button,.gridjs-head button{background-color:transparent;background-image:none;border:none;cursor:pointer;margin:0;outline:none;padding:0}.gridjs-temp{position:relative}.gridjs-head{margin-bottom:5px;padding:5px 1px;width:100%}.gridjs-head:after{clear:both;content:"";display:block}.gridjs-head:empty{border:none;padding:0}.gridjs-container{color:#000;display:inline-block;overflow:hidden;padding:2px;position:relative;z-index:0}.gridjs-footer{background-color:#fff;border-bottom-width:1px;border-color:#e5e7eb;border-radius:0 0 8px 8px;border-top:1px solid #e5e7eb;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.26);display:block;padding:12px 24px;position:relative;width:100%;z-index:5}.gridjs-footer:empty{border:none;padding:0}input.gridjs-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border:1px solid #d2d6dc;border-radius:5px;font-size:14px;line-height:1.45;outline:none;padding:10px 13px}input.gridjs-input:focus{border-color:#9bc2f7;box-shadow:0 0 0 3px rgba(149,189,243,.5)}.gridjs-pagination{color:#3d4044}.gridjs-pagination:after{clear:both;content:"";display:block}.gridjs-pagination .gridjs-summary{float:left;margin-top:5px}.gridjs-pagination .gridjs-pages{float:right}.gridjs-pagination .gridjs-pages button{background-color:#fff;border:1px solid #d2d6dc;border-right:none;outline:none;padding:5px 14px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.gridjs-pagination .gridjs-pages button:focus{border-right:1px solid #d2d6dc;box-shadow:0 0 0 2px rgba(149,189,243,.5);margin-right:-1px;position:relative}.gridjs-pagination .gridjs-pages button:hover{background-color:#f7f7f7;color:#3c4257;outline:none}.gridjs-pagination .gridjs-pages button:disabled,.gridjs-pagination .gridjs-pages button:hover:disabled,.gridjs-pagination .gridjs-pages button[disabled]{background-color:#fff;color:#6b7280;cursor:default}.gridjs-pagination .gridjs-pages button.gridjs-spread{background-color:#fff;box-shadow:none;cursor:default}.gridjs-pagination .gridjs-pages button.gridjs-currentPage{background-color:#f7f7f7;font-weight:700}.gridjs-pagination .gridjs-pages button:last-child{border-bottom-right-radius:6px;border-right:1px solid #d2d6dc;border-top-right-radius:6px}.gridjs-pagination .gridjs-pages button:first-child{border-bottom-left-radius:6px;border-top-left-radius:6px}.gridjs-pagination .gridjs-pages button:last-child:focus{margin-right:0}button.gridjs-sort{background-color:transparent;background-position-x:center;background-repeat:no-repeat;background-size:contain;border:none;cursor:pointer;float:right;height:24px;margin:0;outline:none;padding:0;width:13px}button.gridjs-sort-neutral{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDEuOTk4IiBoZWlnaHQ9IjQwMS45OTgiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQwMS45OTggNDAxLjk5OCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTczLjA5MiAxNjQuNDUyaDI1NS44MTNjNC45NDkgMCA5LjIzMy0xLjgwNyAxMi44NDgtNS40MjQgMy42MTMtMy42MTYgNS40MjctNy44OTggNS40MjctMTIuODQ3cy0xLjgxMy05LjIyOS01LjQyNy0xMi44NUwyMTMuODQ2IDUuNDI0QzIxMC4yMzIgMS44MTIgMjA1Ljk1MSAwIDIwMC45OTkgMHMtOS4yMzMgMS44MTItMTIuODUgNS40MjRMNjAuMjQyIDEzMy4zMzFjLTMuNjE3IDMuNjE3LTUuNDI0IDcuOTAxLTUuNDI0IDEyLjg1IDAgNC45NDggMS44MDcgOS4yMzEgNS40MjQgMTIuODQ3IDMuNjIxIDMuNjE3IDcuOTAyIDUuNDI0IDEyLjg1IDUuNDI0ek0zMjguOTA1IDIzNy41NDlINzMuMDkyYy00Ljk1MiAwLTkuMjMzIDEuODA4LTEyLjg1IDUuNDIxLTMuNjE3IDMuNjE3LTUuNDI0IDcuODk4LTUuNDI0IDEyLjg0N3MxLjgwNyA5LjIzMyA1LjQyNCAxMi44NDhMMTg4LjE0OSAzOTYuNTdjMy42MjEgMy42MTcgNy45MDIgNS40MjggMTIuODUgNS40MjhzOS4yMzMtMS44MTEgMTIuODQ3LTUuNDI4bDEyNy45MDctMTI3LjkwNmMzLjYxMy0zLjYxNCA1LjQyNy03Ljg5OCA1LjQyNy0xMi44NDggMC00Ljk0OC0xLjgxMy05LjIyOS01LjQyNy0xMi44NDctMy42MTQtMy42MTYtNy44OTktNS40Mi0xMi44NDgtNS40MnoiLz48L3N2Zz4=");background-position-y:center;opacity:.3}button.gridjs-sort-asc{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTIuMzYyIiBoZWlnaHQ9IjI5Mi4zNjEiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDI5Mi4zNjIgMjkyLjM2MSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTI4Ni45MzUgMTk3LjI4NyAxNTkuMDI4IDY5LjM4MWMtMy42MTMtMy42MTctNy44OTUtNS40MjQtMTIuODQ3LTUuNDI0cy05LjIzMyAxLjgwNy0xMi44NSA1LjQyNEw1LjQyNCAxOTcuMjg3QzEuODA3IDIwMC45MDQgMCAyMDUuMTg2IDAgMjEwLjEzNHMxLjgwNyA5LjIzMyA1LjQyNCAxMi44NDdjMy42MjEgMy42MTcgNy45MDIgNS40MjUgMTIuODUgNS40MjVoMjU1LjgxM2M0Ljk0OSAwIDkuMjMzLTEuODA4IDEyLjg0OC01LjQyNSAzLjYxMy0zLjYxMyA1LjQyNy03Ljg5OCA1LjQyNy0xMi44NDdzLTEuODE0LTkuMjMtNS40MjctMTIuODQ3eiIvPjwvc3ZnPg==");background-position-y:35%;background-size:10px}button.gridjs-sort-desc{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTIuMzYyIiBoZWlnaHQ9IjI5Mi4zNjIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDI5Mi4zNjIgMjkyLjM2MiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTI4Ni45MzUgNjkuMzc3Yy0zLjYxNC0zLjYxNy03Ljg5OC01LjQyNC0xMi44NDgtNS40MjRIMTguMjc0Yy00Ljk1MiAwLTkuMjMzIDEuODA3LTEyLjg1IDUuNDI0QzEuODA3IDcyLjk5OCAwIDc3LjI3OSAwIDgyLjIyOGMwIDQuOTQ4IDEuODA3IDkuMjI5IDUuNDI0IDEyLjg0N2wxMjcuOTA3IDEyNy45MDdjMy42MjEgMy42MTcgNy45MDIgNS40MjggMTIuODUgNS40MjhzOS4yMzMtMS44MTEgMTIuODQ3LTUuNDI4TDI4Ni45MzUgOTUuMDc0YzMuNjEzLTMuNjE3IDUuNDI3LTcuODk4IDUuNDI3LTEyLjg0NyAwLTQuOTQ4LTEuODE0LTkuMjI5LTUuNDI3LTEyLjg1eiIvPjwvc3ZnPg==");background-position-y:65%;background-size:10px}button.gridjs-sort:focus{outline:none}table.gridjs-table{border-collapse:collapse;display:table;margin:0;max-width:100%;overflow:auto;padding:0;table-layout:fixed;text-align:left;width:100%}.gridjs-tbody,td.gridjs-td{background-color:#fff}td.gridjs-td{border:1px solid #e5e7eb;box-sizing:content-box;padding:12px 24px}td.gridjs-td:first-child{border-left:none}td.gridjs-td:last-child{border-right:none}td.gridjs-message{text-align:center}th.gridjs-th{background-color:#f9fafb;border:1px solid #e5e7eb;border-top:none;box-sizing:border-box;color:#6b7280;outline:none;padding:14px 24px;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}th.gridjs-th .gridjs-th-content{float:left;overflow:hidden;text-overflow:ellipsis;width:100%}th.gridjs-th-sort{cursor:pointer}th.gridjs-th-sort .gridjs-th-content{width:calc(100% - 15px)}th.gridjs-th-sort:focus,th.gridjs-th-sort:hover{background-color:#e5e7eb}th.gridjs-th-fixed{box-shadow:0 1px 0 0 #e5e7eb;position:sticky}@supports (-moz-appearance:none){th.gridjs-th-fixed{box-shadow:0 0 0 1px #e5e7eb}}th.gridjs-th:first-child{border-left:none}th.gridjs-th:last-child{border-right:none}.gridjs-tr{border:none}.gridjs-tr-selected td{background-color:#ebf5ff}.gridjs-tr:last-child td{border-bottom:0}.gridjs *,.gridjs :after,.gridjs :before{box-sizing:border-box}.gridjs-wrapper{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;border-color:#e5e7eb;border-radius:8px 8px 0 0;border-top-width:1px;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.26);display:block;overflow:auto;position:relative;width:100%;z-index:1}.gridjs-wrapper:nth-last-of-type(2){border-bottom-width:1px;border-radius:8px}.gridjs-search{float:left}.gridjs-search-input{width:250px}.gridjs-loading-bar{background-color:#fff;opacity:.5;z-index:10}.gridjs-loading-bar,.gridjs-loading-bar:after{bottom:0;left:0;position:absolute;right:0;top:0}.gridjs-loading-bar:after{animation:shimmer 2s infinite;background-image:linear-gradient(90deg,hsla(0,0%,80%,0),hsla(0,0%,80%,.2) 20%,hsla(0,0%,80%,.5) 60%,hsla(0,0%,80%,0));content:"";transform:translateX(-100%)}@keyframes shimmer{to{transform:translateX(100%)}}.gridjs-td .gridjs-checkbox{cursor:pointer;display:block;margin:auto}.gridjs-resizable{bottom:0;position:absolute;right:0;top:0;width:5px}.gridjs-resizable:hover{background-color:#9bc2f7;cursor:ew-resize}
2 |
3 | .gridjs-footer button,.gridjs-head button{background-color:transparent;background-image:none;border:none;cursor:pointer;margin:0;outline:none;padding:0}.gridjs-temp{position:relative}.gridjs-head{margin-bottom:5px;padding:5px 1px;width:100%}.gridjs-head:after{clear:both;content:"";display:block}.gridjs-head:empty{border:none;padding:0}.gridjs-container{color:#000;display:inline-block;overflow:hidden;padding:2px;position:relative;z-index:0}.gridjs-footer{background-color:#fff;border-bottom-width:1px;border-color:#e5e7eb;border-radius:0 0 8px 8px;border-top:1px solid #e5e7eb;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.26);display:block;padding:12px 24px;position:relative;width:100%;z-index:5}.gridjs-footer:empty{border:none;padding:0}input.gridjs-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border:1px solid #d2d6dc;border-radius:5px;font-size:14px;line-height:1.45;outline:none;padding:10px 13px}input.gridjs-input:focus{border-color:#9bc2f7;box-shadow:0 0 0 3px rgba(149,189,243,.5)}.gridjs-pagination{color:#3d4044}.gridjs-pagination:after{clear:both;content:"";display:block}.gridjs-pagination .gridjs-summary{float:left;margin-top:5px}.gridjs-pagination .gridjs-pages{float:right}.gridjs-pagination .gridjs-pages button{background-color:#fff;border:1px solid #d2d6dc;border-right:none;outline:none;padding:5px 14px;-webkit-user-select:none;-moz-user-select:none;user-select:none}.gridjs-pagination .gridjs-pages button:focus{border-right:1px solid #d2d6dc;box-shadow:0 0 0 2px rgba(149,189,243,.5);margin-right:-1px;position:relative}.gridjs-pagination .gridjs-pages button:hover{background-color:#f7f7f7;color:#3c4257;outline:none}.gridjs-pagination .gridjs-pages button:disabled,.gridjs-pagination .gridjs-pages button:hover:disabled,.gridjs-pagination .gridjs-pages button[disabled]{background-color:#fff;color:#6b7280;cursor:default}.gridjs-pagination .gridjs-pages button.gridjs-spread{background-color:#fff;box-shadow:none;cursor:default}.gridjs-pagination .gridjs-pages button.gridjs-currentPage{background-color:#f7f7f7;font-weight:700}.gridjs-pagination .gridjs-pages button:last-child{border-bottom-right-radius:6px;border-right:1px solid #d2d6dc;border-top-right-radius:6px}.gridjs-pagination .gridjs-pages button:first-child{border-bottom-left-radius:6px;border-top-left-radius:6px}.gridjs-pagination .gridjs-pages button:last-child:focus{margin-right:0}button.gridjs-sort{background-color:transparent;background-position-x:center;background-repeat:no-repeat;background-size:contain;border:none;cursor:pointer;float:right;height:24px;margin:0;outline:none;padding:0;width:13px}button.gridjs-sort-neutral{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MDEuOTk4IiBoZWlnaHQ9IjQwMS45OTgiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDQwMS45OTggNDAxLjk5OCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTczLjA5MiAxNjQuNDUyaDI1NS44MTNjNC45NDkgMCA5LjIzMy0xLjgwNyAxMi44NDgtNS40MjQgMy42MTMtMy42MTYgNS40MjctNy44OTggNS40MjctMTIuODQ3cy0xLjgxMy05LjIyOS01LjQyNy0xMi44NUwyMTMuODQ2IDUuNDI0QzIxMC4yMzIgMS44MTIgMjA1Ljk1MSAwIDIwMC45OTkgMHMtOS4yMzMgMS44MTItMTIuODUgNS40MjRMNjAuMjQyIDEzMy4zMzFjLTMuNjE3IDMuNjE3LTUuNDI0IDcuOTAxLTUuNDI0IDEyLjg1IDAgNC45NDggMS44MDcgOS4yMzEgNS40MjQgMTIuODQ3IDMuNjIxIDMuNjE3IDcuOTAyIDUuNDI0IDEyLjg1IDUuNDI0ek0zMjguOTA1IDIzNy41NDlINzMuMDkyYy00Ljk1MiAwLTkuMjMzIDEuODA4LTEyLjg1IDUuNDIxLTMuNjE3IDMuNjE3LTUuNDI0IDcuODk4LTUuNDI0IDEyLjg0N3MxLjgwNyA5LjIzMyA1LjQyNCAxMi44NDhMMTg4LjE0OSAzOTYuNTdjMy42MjEgMy42MTcgNy45MDIgNS40MjggMTIuODUgNS40MjhzOS4yMzMtMS44MTEgMTIuODQ3LTUuNDI4bDEyNy45MDctMTI3LjkwNmMzLjYxMy0zLjYxNCA1LjQyNy03Ljg5OCA1LjQyNy0xMi44NDggMC00Ljk0OC0xLjgxMy05LjIyOS01LjQyNy0xMi44NDctMy42MTQtMy42MTYtNy44OTktNS40Mi0xMi44NDgtNS40MnoiLz48L3N2Zz4=");background-position-y:center;opacity:.3}button.gridjs-sort-asc{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTIuMzYyIiBoZWlnaHQ9IjI5Mi4zNjEiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDI5Mi4zNjIgMjkyLjM2MSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTI4Ni45MzUgMTk3LjI4NyAxNTkuMDI4IDY5LjM4MWMtMy42MTMtMy42MTctNy44OTUtNS40MjQtMTIuODQ3LTUuNDI0cy05LjIzMyAxLjgwNy0xMi44NSA1LjQyNEw1LjQyNCAxOTcuMjg3QzEuODA3IDIwMC45MDQgMCAyMDUuMTg2IDAgMjEwLjEzNHMxLjgwNyA5LjIzMyA1LjQyNCAxMi44NDdjMy42MjEgMy42MTcgNy45MDIgNS40MjUgMTIuODUgNS40MjVoMjU1LjgxM2M0Ljk0OSAwIDkuMjMzLTEuODA4IDEyLjg0OC01LjQyNSAzLjYxMy0zLjYxMyA1LjQyNy03Ljg5OCA1LjQyNy0xMi44NDdzLTEuODE0LTkuMjMtNS40MjctMTIuODQ3eiIvPjwvc3ZnPg==");background-position-y:35%;background-size:10px}button.gridjs-sort-desc{background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyOTIuMzYyIiBoZWlnaHQ9IjI5Mi4zNjIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDI5Mi4zNjIgMjkyLjM2MiIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTI4Ni45MzUgNjkuMzc3Yy0zLjYxNC0zLjYxNy03Ljg5OC01LjQyNC0xMi44NDgtNS40MjRIMTguMjc0Yy00Ljk1MiAwLTkuMjMzIDEuODA3LTEyLjg1IDUuNDI0QzEuODA3IDcyLjk5OCAwIDc3LjI3OSAwIDgyLjIyOGMwIDQuOTQ4IDEuODA3IDkuMjI5IDUuNDI0IDEyLjg0N2wxMjcuOTA3IDEyNy45MDdjMy42MjEgMy42MTcgNy45MDIgNS40MjggMTIuODUgNS40MjhzOS4yMzMtMS44MTEgMTIuODQ3LTUuNDI4TDI4Ni45MzUgOTUuMDc0YzMuNjEzLTMuNjE3IDUuNDI3LTcuODk4IDUuNDI3LTEyLjg0NyAwLTQuOTQ4LTEuODE0LTkuMjI5LTUuNDI3LTEyLjg1eiIvPjwvc3ZnPg==");background-position-y:65%;background-size:10px}button.gridjs-sort:focus{outline:none}table.gridjs-table{border-collapse:collapse;display:table;margin:0;max-width:100%;overflow:auto;padding:0;table-layout:fixed;text-align:left;width:100%}.gridjs-tbody,td.gridjs-td{background-color:#fff}td.gridjs-td{border:1px solid #e5e7eb;box-sizing:content-box;padding:12px 24px}td.gridjs-td:first-child{border-left:none}td.gridjs-td:last-child{border-right:none}td.gridjs-message{text-align:center}th.gridjs-th{background-color:#f9fafb;border:1px solid #e5e7eb;border-top:none;box-sizing:border-box;color:#6b7280;outline:none;padding:14px 24px;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}th.gridjs-th .gridjs-th-content{float:left;overflow:hidden;text-overflow:ellipsis;width:100%}th.gridjs-th-sort{cursor:pointer}th.gridjs-th-sort .gridjs-th-content{width:calc(100% - 15px)}th.gridjs-th-sort:focus,th.gridjs-th-sort:hover{background-color:#e5e7eb}th.gridjs-th-fixed{box-shadow:0 1px 0 0 #e5e7eb;position:sticky}@supports (-moz-appearance:none){th.gridjs-th-fixed{box-shadow:0 0 0 1px #e5e7eb}}th.gridjs-th:first-child{border-left:none}th.gridjs-th:last-child{border-right:none}.gridjs-tr{border:none}.gridjs-tr-selected td{background-color:#ebf5ff}.gridjs-tr:last-child td{border-bottom:0}.gridjs *,.gridjs :after,.gridjs :before{box-sizing:border-box}.gridjs-wrapper{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;border-color:#e5e7eb;border-radius:8px 8px 0 0;border-top-width:1px;box-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px 0 rgba(0,0,0,.26);display:block;overflow:auto;position:relative;width:100%;z-index:1}.gridjs-wrapper:nth-last-of-type(2){border-bottom-width:1px;border-radius:8px}.gridjs-search{float:left}.gridjs-search-input{width:250px}.gridjs-loading-bar{background-color:#fff;opacity:.5;z-index:10}.gridjs-loading-bar,.gridjs-loading-bar:after{bottom:0;left:0;position:absolute;right:0;top:0}.gridjs-loading-bar:after{animation:shimmer 2s infinite;background-image:linear-gradient(90deg,hsla(0,0%,80%,0),hsla(0,0%,80%,.2) 20%,hsla(0,0%,80%,.5) 60%,hsla(0,0%,80%,0));content:"";transform:translateX(-100%)}@keyframes shimmer{to{transform:translateX(100%)}}.gridjs-td .gridjs-checkbox{cursor:pointer;display:block;margin:auto}.gridjs-resizable{bottom:0;position:absolute;right:0;top:0;width:5px}.gridjs-resizable:hover{background-color:#9bc2f7;cursor:ew-resize}
4 |
5 |
6 | .f32 .flag{display:inline-block;height:32px;width:32px;vertical-align:text-top;line-height:32px;background:url(../images/flags32.png) no-repeat;}
7 | .f32 ._African_Union{background-position:0 -32px;}
8 | .f32 ._Arab_League{background-position:0 -64px;}
9 | .f32 ._ASEAN{background-position:0 -96px;}
10 | .f32 ._CARICOM{background-position:0 -128px;}
11 | .f32 ._CIS{background-position:0 -160px;}
12 | .f32 ._Commonwealth{background-position:0 -192px;}
13 | .f32 ._England{background-position:0 -224px;}
14 | .f32 ._European_Union, .f32 .eu{background-position:0 -256px;}
15 | .f32 ._Islamic_Conference{background-position:0 -288px;}
16 | .f32 ._Kosovo{background-position:0 -320px;}
17 | .f32 ._NATO{background-position:0 -352px;}
18 | .f32 ._Northern_Cyprus{background-position:0 -384px;}
19 | .f32 ._Northern_Ireland{background-position:0 -416px;}
20 | .f32 ._Olimpic_Movement{background-position:0 -448px;}
21 | .f32 ._OPEC{background-position:0 -480px;}
22 | .f32 ._Red_Cross{background-position:0 -512px;}
23 | .f32 ._Scotland{background-position:0 -544px;}
24 | .f32 ._Somaliland{background-position:0 -576px;}
25 | .f32 ._Tibet{background-position:0 -608px;}
26 | .f32 ._United_Nations{background-position:0 -640px;}
27 | .f32 ._Wales{background-position:0 -672px;}
28 | .f32 .ad, .f32 .and {background-position: 0 -704px;}
29 | .f32 .ae, .f32 .are {background-position: 0 -736px;}
30 | .f32 .af, .f32 .afg {background-position: 0 -768px;}
31 | .f32 .ag, .f32 .atg {background-position: 0 -800px;}
32 | .f32 .ai, .f32 .aia {background-position: 0 -832px;}
33 | .f32 .al, .f32 .alb {background-position: 0 -864px;}
34 | .f32 .am, .f32 .arm {background-position: 0 -896px;}
35 | .f32 .ao, .f32 .ago {background-position: 0 -928px;}
36 | .f32 .aq, .f32 .ata {background-position: 0 -960px;}
37 | .f32 .ar, .f32 .arg {background-position: 0 -992px;}
38 | .f32 .as, .f32 .asm {background-position: 0 -1024px;}
39 | .f32 .at, .f32 .aut {background-position: 0 -1056px;}
40 | .f32 .au, .f32 .aus {background-position: 0 -1088px;}
41 | .f32 .aw, .f32 .abw {background-position: 0 -1120px;}
42 | .f32 .ax, .f32 .ala {background-position: 0 -1152px;}
43 | .f32 .az, .f32 .aze {background-position: 0 -1184px;}
44 | .f32 .ba, .f32 .bih {background-position: 0 -1216px;}
45 | .f32 .bb, .f32 .brb {background-position: 0 -1248px;}
46 | .f32 .bd, .f32 .bgd {background-position: 0 -1280px;}
47 | .f32 .be, .f32 .bel {background-position: 0 -1312px;}
48 | .f32 .bf, .f32 .bfa {background-position: 0 -1344px;}
49 | .f32 .bg, .f32 .bgr {background-position: 0 -1376px;}
50 | .f32 .bh, .f32 .bhr {background-position: 0 -1408px;}
51 | .f32 .bi, .f32 .bdi {background-position: 0 -1440px;}
52 | .f32 .bj, .f32 .ben {background-position: 0 -1472px;}
53 | .f32 .bm, .f32 .bmu {background-position: 0 -1504px;}
54 | .f32 .bn, .f32 .brn {background-position: 0 -1536px;}
55 | .f32 .bo, .f32 .bol {background-position: 0 -1568px;}
56 | .f32 .br, .f32 .bra {background-position: 0 -1600px;}
57 | .f32 .bs, .f32 .bhs {background-position: 0 -1632px;}
58 | .f32 .bt, .f32 .btn {background-position: 0 -1664px;}
59 | .f32 .bw, .f32 .bwa {background-position: 0 -1696px;}
60 | .f32 .by, .f32 .blr {background-position: 0 -1728px;}
61 | .f32 .bz, .f32 .blz {background-position: 0 -1760px;}
62 | .f32 .ca, .f32 .can {background-position: 0 -1792px;}
63 | .f32 .cd, .f32 .cod {background-position: 0 -1824px;}
64 | .f32 .cf, .f32 .rca {background-position: 0 -1856px;}
65 | .f32 .cg, .f32 .cog {background-position: 0 -1888px;}
66 | .f32 .ch, .f32 .che {background-position: 0 -1920px;}
67 | .f32 .ci, .f32 .civ {background-position: 0 -1952px;}
68 | .f32 .ck, .f32 .cok {background-position: 0 -1984px;}
69 | .f32 .cl, .f32 .chl {background-position: 0 -2016px;}
70 | .f32 .cm, .f32 .cmr {background-position: 0 -2048px;}
71 | .f32 .cn, .f32 .chn {background-position: 0 -2080px;}
72 | .f32 .co, .f32 .col {background-position: 0 -2112px;}
73 | .f32 .cr, .f32 .cri {background-position: 0 -2144px;}
74 | .f32 .cu, .f32 .cub {background-position: 0 -2176px;}
75 | .f32 .cv, .f32 .cpv {background-position: 0 -2208px;}
76 | .f32 .cy, .f32 .cyp {background-position: 0 -2240px;}
77 | .f32 .cz, .f32 .cze {background-position: 0 -2272px;}
78 | .f32 .de, .f32 .deu {background-position: 0 -2304px;}
79 | .f32 .dj, .f32 .dji {background-position: 0 -2336px;}
80 | .f32 .dk, .f32 .dnk {background-position: 0 -2368px;}
81 | .f32 .dm, .f32 .dma {background-position: 0 -2400px;}
82 | .f32 .do, .f32 .dom {background-position: 0 -2432px;}
83 | .f32 .dz, .f32 .dza {background-position: 0 -2464px;}
84 | .f32 .ec, .f32 .ecu {background-position: 0 -2496px;}
85 | .f32 .ee, .f32 .est {background-position: 0 -2528px;}
86 | .f32 .eg, .f32 .egy {background-position: 0 -2560px;}
87 | .f32 .eh, .f32 .esh {background-position: 0 -2592px;}
88 | .f32 .er, .f32 .eri {background-position: 0 -2624px;}
89 | .f32 .es, .f32 .esp {background-position: 0 -2656px;}
90 | .f32 .et, .f32 .eth {background-position: 0 -2688px;}
91 | .f32 .fi, .f32 .fin {background-position: 0 -2720px;}
92 | .f32 .fj, .f32 .fji {background-position: 0 -2752px;}
93 | .f32 .fm, .f32 .fsm {background-position: 0 -2784px;}
94 | .f32 .fo, .f32 .fro {background-position: 0 -2816px;}
95 | .f32 .fr, .f32 .fra {background-position: 0 -2848px;}
96 | .f32 .bl, .f32 .blm {background-position: 0 -2848px;}
97 | .f32 .cp, .f32 .cpt {background-position: 0 -2848px;}
98 | .f32 .mf, .f32 .maf {background-position: 0 -2848px;}
99 | .f32 .yt, .f32 .myt {background-position: 0 -2848px;}
100 | .f32 .ga, .f32 .gab {background-position: 0 -2880px;}
101 | .f32 .gb, .f32 .gbr {background-position: 0 -2912px;}
102 | .f32 .sh, .f32 .shn {background-position: 0 -2912px;}
103 | .f32 .gd, .f32 .grd {background-position: 0 -2944px;}
104 | .f32 .ge, .f32 .geo {background-position: 0 -2976px;}
105 | .f32 .gg, .f32 .ggy {background-position: 0 -3008px;}
106 | .f32 .gh, .f32 .gha {background-position: 0 -3040px;}
107 | .f32 .gi, .f32 .gib {background-position: 0 -3072px;}
108 | .f32 .gl, .f32 .grl {background-position: 0 -3104px;}
109 | .f32 .gm, .f32 .gmb {background-position: 0 -3136px;}
110 | .f32 .gn, .f32 .gin {background-position: 0 -3168px;}
111 | .f32 .gp, .f32 .glp {background-position: 0 -3200px;}
112 | .f32 .gq, .f32 .gnq {background-position: 0 -3232px;}
113 | .f32 .gr, .f32 .grc {background-position: 0 -3264px;}
114 | .f32 .gt, .f32 .gtm {background-position: 0 -3296px;}
115 | .f32 .gu, .f32 .gum {background-position: 0 -3328px;}
116 | .f32 .gw, .f32 .gnb {background-position: 0 -3360px;}
117 | .f32 .gy, .f32 .guy {background-position: 0 -3392px;}
118 | .f32 .hk, .f32 .hkg {background-position: 0 -3424px;}
119 | .f32 .hn, .f32 .hnd {background-position: 0 -3456px;}
120 | .f32 .hr, .f32 .hrv {background-position: 0 -3488px;}
121 | .f32 .ht, .f32 .hti {background-position: 0 -3520px;}
122 | .f32 .hu, .f32 .hun {background-position: 0 -3552px;}
123 | .f32 .id, .f32 .idn {background-position: 0 -3584px;}
124 | .f32 .mc, .f32 .mco {background-position: 0 -3584px;}
125 | .f32 .ie {background-position: 0 -3616px;}
126 | .f32 .il, .f32 .isr {background-position: 0 -3648px;}
127 | .f32 .im, .f32 .imn {background-position: 0 -3680px;}
128 | .f32 .in, .f32 .ind {background-position: 0 -3712px;}
129 | .f32 .iq, .f32 .irq {background-position: 0 -3744px;}
130 | .f32 .ir, .f32 .irn {background-position: 0 -3776px;}
131 | .f32 .is, .f32 .isl {background-position: 0 -3808px;}
132 | .f32 .it, .f32 .ita {background-position: 0 -3840px;}
133 | .f32 .je, .f32 .jey {background-position: 0 -3872px;}
134 | .f32 .jm, .f32 .jam {background-position: 0 -3904px;}
135 | .f32 .jo, .f32 .jor {background-position: 0 -3936px;}
136 | .f32 .jp, .f32 .jpn {background-position: 0 -3968px;}
137 | .f32 .ke, .f32 .ken {background-position: 0 -4000px;}
138 | .f32 .kg, .f32 .kgz {background-position: 0 -4032px;}
139 | .f32 .kh, .f32 .khm {background-position: 0 -4064px;}
140 | .f32 .ki, .f32 .kir {background-position: 0 -4096px;}
141 | .f32 .km, .f32 .com {background-position: 0 -4128px;}
142 | .f32 .kn, .f32 .kna {background-position: 0 -4160px;}
143 | .f32 .kp, .f32 .prk {background-position: 0 -4192px;}
144 | .f32 .kr, .f32 .kor {background-position: 0 -4224px;}
145 | .f32 .kw, .f32 .kwt {background-position: 0 -4256px;}
146 | .f32 .ky, .f32 .cym {background-position: 0 -4288px;}
147 | .f32 .kz, .f32 .kaz {background-position: 0 -4320px;}
148 | .f32 .la, .f32 .lao {background-position: 0 -4352px;}
149 | .f32 .lb, .f32 .lbn {background-position: 0 -4384px;}
150 | .f32 .lc, .f32 .lca {background-position: 0 -4416px;}
151 | .f32 .li, .f32 .lie {background-position: 0 -4448px;}
152 | .f32 .lk, .f32 .lka {background-position: 0 -4480px;}
153 | .f32 .lr, .f32 .lbr {background-position: 0 -4512px;}
154 | .f32 .ls, .f32 .lso {background-position: 0 -4544px;}
155 | .f32 .lt, .f32 .ltu {background-position: 0 -4576px;}
156 | .f32 .lu, .f32 .lux {background-position: 0 -4608px;}
157 | .f32 .lv, .f32 .lva {background-position: 0 -4640px;}
158 | .f32 .ly, .f32 .lby {background-position: 0 -4672px;}
159 | .f32 .ma, .f32 .mar {background-position: 0 -4704px;}
160 | .f32 .md, .f32 .mda {background-position: 0 -4736px;}
161 | .f32 .me, .f32 .mne {background-position: 0 -4768px;}
162 | .f32 .mg, .f32 .mdg {background-position: 0 -4800px;}
163 | .f32 .mh, .f32 .mhl {background-position: 0 -4832px;}
164 | .f32 .mk, .f32 .mkd {background-position: 0 -4864px;}
165 | .f32 .ml, .f32 .mli {background-position: 0 -4896px;}
166 | .f32 .mm, .f32 .mmr {background-position: 0 -4928px;}
167 | .f32 .mn, .f32 .mng {background-position: 0 -4960px;}
168 | .f32 .mo, .f32 .mac {background-position: 0 -4992px;}
169 | .f32 .mq, .f32 .mtq {background-position: 0 -5024px;}
170 | .f32 .mr, .f32 .mrt {background-position: 0 -5056px;}
171 | .f32 .ms, .f32 .msr {background-position: 0 -5088px;}
172 | .f32 .mt, .f32 .mlt {background-position: 0 -5120px;}
173 | .f32 .mu, .f32 .mus {background-position: 0 -5152px;}
174 | .f32 .mv, .f32 .mdv {background-position: 0 -5184px;}
175 | .f32 .mw, .f32 .mwi {background-position: 0 -5216px;}
176 | .f32 .mx, .f32 .mex {background-position: 0 -5248px;}
177 | .f32 .my, .f32 .mys {background-position: 0 -5280px;}
178 | .f32 .mz, .f32 .moz {background-position: 0 -5312px;}
179 | .f32 .na, .f32 .nam {background-position: 0 -5344px;}
180 | .f32 .nc, .f32 .ncl {background-position: 0 -5376px;}
181 | .f32 .ne, .f32 .ner {background-position: 0 -5408px;}
182 | .f32 .ng, .f32 .nga {background-position: 0 -5440px;}
183 | .f32 .ni, .f32 .nic {background-position: 0 -5472px;}
184 | .f32 .nl, .f32 .nld {background-position: 0 -5504px;}
185 | .f32 .nl{background-position:0 -5504px;} .f32 .bq{background-position:0 -5504px;}.f32 .no{background-position:0 -5536px;} .f32 .bv, .f32 .nq, .f32 .sj{background-position:0 -5536px;}
186 | .f32 .bq, .f32 .bes {background-position: 0 -5504px;}
187 | .f32 .no, .f32 .nor {background-position: 0 -5536px;}
188 | .f32 .bv, .f32 .bvt {background-position: 0 -5536px;}
189 | .f32 .nq, .f32 .atn {background-position: 0 -5536px;}
190 | .f32 .sj, .f32 .sjm {background-position: 0 -5536px;}
191 | .f32 .np, .f32 .npl {background-position: 0 -5568px;}
192 | .f32 .nr, .f32 .nru {background-position: 0 -5600px;}
193 | .f32 .nz, .f32 .nzl {background-position: 0 -5632px;}
194 | .f32 .om, .f32 .omn {background-position: 0 -5664px;}
195 | .f32 .pa, .f32 .pan {background-position: 0 -5696px;}
196 | .f32 .pe, .f32 .per {background-position: 0 -5728px;}
197 | .f32 .pf, .f32 .pyf {background-position: 0 -5760px;}
198 | .f32 .pg, .f32 .png {background-position: 0 -5792px;}
199 | .f32 .ph, .f32 .phl {background-position: 0 -5824px;}
200 | .f32 .pk, .f32 .pak {background-position: 0 -5856px;}
201 | .f32 .pl, .f32 .pol {background-position: 0 -5888px;}
202 | .f32 .pr, .f32 .pri {background-position: 0 -5920px;}
203 | .f32 .ps, .f32 .pse {background-position: 0 -5952px;}
204 | .f32 .pt, .f32 .prt {background-position: 0 -5984px;}
205 | .f32 .pw, .f32 .plw {background-position: 0 -6016px;}
206 | .f32 .py, .f32 .pry {background-position: 0 -6048px;}
207 | .f32 .qa, .f32 .qat {background-position: 0 -6080px;}
208 | .f32 .re, .f32 .reu {background-position: 0 -6112px;}
209 | .f32 .ro, .f32 .rou {background-position: 0 -6144px;}
210 | .f32 .rs, .f32 .srb {background-position: 0 -6176px;}
211 | .f32 .ru, .f32 .rus {background-position: 0 -6208px;}
212 | .f32 .rw, .f32 .rwa {background-position: 0 -6240px;}
213 | .f32 .sa, .f32 .sau {background-position: 0 -6272px;}
214 | .f32 .sb, .f32 .slb {background-position: 0 -6304px;}
215 | .f32 .sc, .f32 .syc {background-position: 0 -6336px;}
216 | .f32 .sd, .f32 .sdn {background-position: 0 -6368px;}
217 | .f32 .se, .f32 .swe {background-position: 0 -6400px;}
218 | .f32 .sg, .f32 .sgp {background-position: 0 -6432px;}
219 | .f32 .si, .f32 .svn {background-position: 0 -6464px;}
220 | .f32 .sk, .f32 .svk {background-position: 0 -6496px;}
221 | .f32 .sl, .f32 .sle {background-position: 0 -6528px;}
222 | .f32 .sm, .f32 .smr {background-position: 0 -6560px;}
223 | .f32 .sn, .f32 .sen {background-position: 0 -6592px;}
224 | .f32 .so, .f32 .som {background-position: 0 -6624px;}
225 | .f32 .sr, .f32 .sur {background-position: 0 -6656px;}
226 | .f32 .st, .f32 .stp {background-position: 0 -6688px;}
227 | .f32 .sv, .f32 .slv {background-position: 0 -6720px;}
228 | .f32 .sy, .f32 .syr {background-position: 0 -6752px;}
229 | .f32 .sz, .f32 .swz {background-position: 0 -6784px;}
230 | .f32 .tc, .f32 .tca {background-position: 0 -6816px;}
231 | .f32 .td, .f32 .tcd {background-position: 0 -6848px;}
232 | .f32 .tg, .f32 .tgo {background-position: 0 -6880px;}
233 | .f32 .th, .f32 .tha {background-position: 0 -6912px;}
234 | .f32 .tj, .f32 .tjk {background-position: 0 -6944px;}
235 | .f32 .tl, .f32 .tls {background-position: 0 -6976px;}
236 | .f32 .tm, .f32 .tkm {background-position: 0 -7008px;}
237 | .f32 .tn, .f32 .tun {background-position: 0 -7040px;}
238 | .f32 .to, .f32 .ton {background-position: 0 -7072px;}
239 | .f32 .tr, .f32 .tur {background-position: 0 -7104px;}
240 | .f32 .tt, .f32 .tto {background-position: 0 -7136px;}
241 | .f32 .tv, .f32 .tuv {background-position: 0 -7168px;}
242 | .f32 .tw, .f32 .twn {background-position: 0 -7200px;}
243 | .f32 .tz, .f32 .tza {background-position: 0 -7232px;}
244 | .f32 .ua, .f32 .ukr {background-position: 0 -7264px;}
245 | .f32 .ug, .f32 .uga {background-position: 0 -7296px;}
246 | .f32 .us, .f32 .usa {background-position: 0 -7328px;}
247 | .f32 .uy, .f32 .ury {background-position: 0 -7360px;}
248 | .f32 .uz, .f32 .uzb {background-position: 0 -7392px;}
249 | .f32 .va, .f32 .vat {background-position: 0 -7424px;}
250 | .f32 .vc, .f32 .vct {background-position: 0 -7456px;}
251 | .f32 .ve, .f32 .ven {background-position: 0 -7488px;}
252 | .f32 .vg, .f32 .vgb {background-position: 0 -7520px;}
253 | .f32 .vi, .f32 .vir {background-position: 0 -7552px;}
254 | .f32 .vn, .f32 .vnm {background-position: 0 -7584px;}
255 | .f32 .vu, .f32 .vut {background-position: 0 -7616px;}
256 | .f32 .ws, .f32 .wsm {background-position: 0 -7648px;}
257 | .f32 .ye, .f32 .yem {background-position: 0 -7680px;}
258 | .f32 .za, .f32 .zaf {background-position: 0 -7712px;}
259 | .f32 .zm, .f32 .zmb {background-position: 0 -7744px;}
260 | .f32 .zw, .f32 .zwe {background-position: 0 -7776px;}
261 | .f32 .sx, .f32 .sxm {background-position: 0 -7808px;}
262 | .f32 .cw, .f32 .cuw {background-position: 0 -7840px;}
263 | .f32 .ss, .f32 .ssd {background-position: 0 -7872px;}
264 | .f32 .nu, .f32 .niu {background-position: 0 -7904px;}
265 |
--------------------------------------------------------------------------------
/view/adminhtml/web/images/flag32.png:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/view/adminhtml/web/images/flags32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Genaker/magento_gridjs/b5c1f352cb0ca7116134ab3335d4dff6139e6863/view/adminhtml/web/images/flags32.png
--------------------------------------------------------------------------------
/view/adminhtml/web/js/preact.js:
--------------------------------------------------------------------------------
1 | /* esm.sh - esbuild bundle(htm@3.1.1/preact/standalone) es2022 production */
2 | var N,f,en,D,tn,B,_n,F={},rn=[],yn=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function C(e,n){for(var t in n)e[t]=n[t];return e}function on(e){var n=e.parentNode;n&&n.removeChild(e)}function ln(e,n,t){var _,l,r,u={};for(r in n)r=="key"?_=n[r]:r=="ref"?l=n[r]:u[r]=n[r];if(arguments.length>2&&(u.children=arguments.length>3?N.call(arguments,2):t),typeof e=="function"&&e.defaultProps!=null)for(r in e.defaultProps)u[r]===void 0&&(u[r]=e.defaultProps[r]);return U(e,u,_,l,null)}function U(e,n,t,_,l){var r={type:e,props:n,key:t,ref:_,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:l??++en};return f.vnode!=null&&f.vnode(r),r}function W(e){return e.children}function A(e,n){this.props=e,this.context=n}function E(e,n){if(n==null)return e.__?E(e.__,e.__.__k.indexOf(e)+1):null;for(var t;n0?U(i.type,i.props,i.key,null,i.__v):i)!=null){if(i.__=t,i.__b=t.__b+1,(c=y[o])===null||c&&i.key==c.key&&i.type===c.type)y[o]=void 0;else for(m=0;m=t.__.length&&t.__.push({}),t.__[e]}function bn(e){return S=1,Cn(vn,e)}function Cn(e,n,t){var _=P(x++,2);return _.t=e,_.__c||(_.__=[t?t(n):vn(void 0,n),function(l){var r=_.t(_.__[0],l);_.__[0]!==r&&(_.__=[r,_.__[1]],_.__c.setState({}))}],_.__c=g),_.__}function Pn(e,n){var t=P(x++,3);!f.__s&&q(t.__H,n)&&(t.__=e,t.__H=n,g.__H.__h.push(t))}function xn(e,n){var t=P(x++,4);!f.__s&&q(t.__H,n)&&(t.__=e,t.__H=n,g.__h.push(t))}function Dn(e){return S=5,dn(function(){return{current:e}},[])}function wn(e,n,t){S=6,xn(function(){typeof e=="function"?e(n()):e&&(e.current=n())},t==null?t:t.concat(e))}function dn(e,n){var t=P(x++,7);return q(t.__H,n)&&(t.__=e(),t.__H=n,t.__h=e),t.__}function Tn(e,n){return S=8,dn(function(){return e},n)}function Un(e){var n=g.context[e.__c],t=P(x++,9);return t.c=e,n?(t.__==null&&(t.__=!0,n.sub(g)),n.props.value):e.__}function An(e,n){f.useDebugValue&&f.useDebugValue(n?n(e):e)}function Mn(e){var n=P(x++,10),t=bn();return n.__=e,g.componentDidCatch||(g.componentDidCatch=function(_){n.__&&n.__(_),t[1](_)}),[t[0],function(){t[1](void 0)}]}function Hn(){I.forEach(function(e){if(e.__P)try{e.__H.__h.forEach(M),e.__H.__h.forEach(O),e.__H.__h=[]}catch(n){e.__H.__h=[],f.__e(n,e.__v)}}),I=[]}f.__b=function(e){g=null,J&&J(e)},f.__r=function(e){K&&K(e),x=0;var n=(g=e.__c).__H;n&&(n.__h.forEach(M),n.__h.forEach(O),n.__h=[])},f.diffed=function(e){Q&&Q(e);var n=e.__c;n&&n.__H&&n.__H.__h.length&&(I.push(n)!==1&&z===f.requestAnimationFrame||((z=f.requestAnimationFrame)||function(t){var _,l=function(){clearTimeout(r),Z&&cancelAnimationFrame(_),setTimeout(t)},r=setTimeout(l,100);Z&&(_=requestAnimationFrame(l))})(Hn)),g=void 0},f.__c=function(e,n){n.some(function(t){try{t.__h.forEach(M),t.__h=t.__h.filter(function(_){return!_.__||O(_)})}catch(_){n.some(function(l){l.__h&&(l.__h=[])}),n=[],f.__e(_,t.__v)}}),X&&X(e,n)},f.unmount=function(e){Y&&Y(e);var n=e.__c;if(n&&n.__H)try{n.__H.__.forEach(M)}catch(t){f.__e(t,n.__v)}};var Z=typeof requestAnimationFrame=="function";function M(e){var n=g;typeof e.__c=="function"&&e.__c(),g=n}function O(e){var n=g;e.__c=e.__(),g=n}function q(e,n){return!e||e.length!==n.length||n.some(function(t,_){return t!==e[_]})}function vn(e,n){return typeof n=="function"?n(e):n}var mn=function(e,n,t,_){var l;n[0]=0;for(var r=1;r=5&&((u||!c&&r===5)&&(s.push(r,0,u,l),r=6),c&&(s.push(r,c,0,l),r=6)),u=""},o=0;o"?(r=1,u=""):u=_+u[0]:a?_===a?a="":u+=_:_==='"'||_==="'"?a=_:_===">"?(h(),r=1):r&&(_==="="?(r=5,l=u,u=""):_==="/"&&(r<5||t[o][m+1]===">")?(h(),r===3&&(s=s[0]),r=s,(s=s[0]).push(2,0,r),r=0):_===" "||_===" "||_===`
3 | `||_==="\r"?(h(),r=2):u+=_),r===3&&u==="!--"&&(r=4,s=s[0])}return h(),s}(e)),n),arguments,[])).length>1?n:n[0]}.bind(ln);export{A as Component,Sn as createContext,ln as h,Fn as html,En as render,Tn as useCallback,Un as useContext,An as useDebugValue,Pn as useEffect,Mn as useErrorBoundary,wn as useImperativeHandle,xn as useLayoutEffect,dn as useMemo,Cn as useReducer,Dn as useRef,bn as useState};
4 | //# sourceMappingURL=standalone.js.map
--------------------------------------------------------------------------------