├── 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 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 | 5 | 12 | 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 | getFiltersHtml($filterData) ?> 40 | 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | 56 | 57 |
58 |
59 | 60 | 61 | 62 |
63 |
64 | Server SQL Query time: 65 | - 66 |
67 |
68 | Server Count time: 69 | - 70 |
71 |
72 | 73 | 307 | 308 | 345 | 346 | 347 | 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 --------------------------------------------------------------------------------