diff --git a/composer.json b/composer.json
index 51a8933..b9d9a63 100644
--- a/composer.json
+++ b/composer.json
@@ -15,6 +15,11 @@
"require-dev": {
"prestashop/php-dev-tools": "^3.4"
},
+ "autoload": {
+ "psr-4": {
+ "PrestaShop\\Module\\DashActivity\\": "src/"
+ }
+ },
"config": {
"preferred-install": "dist",
"classmap-authoritative": true,
diff --git a/config.xml b/config.xml
index 1e5e281..569bd3f 100644
--- a/config.xml
+++ b/config.xml
@@ -2,11 +2,11 @@
dashactivity
-
-
+
+
-
- 0
+
+ 1
1
diff --git a/config/index.php b/config/index.php
new file mode 100644
index 0000000..45df26c
--- /dev/null
+++ b/config/index.php
@@ -0,0 +1,34 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/config/routes.yml b/config/routes.yml
new file mode 100644
index 0000000..5f78a45
--- /dev/null
+++ b/config/routes.yml
@@ -0,0 +1,7 @@
+dashactivity_configuration:
+ path: /dashactivity/configuration
+ methods: [GET, POST]
+ defaults:
+ _controller: PrestaShop\Module\DashActivity\Controller\ConfigurationController::indexAction
+ _legacy_controller: AdminDashactivityConfiguration
+ _legacy_link: AdminDashactivityConfiguration
diff --git a/config/services.yml b/config/services.yml
new file mode 100644
index 0000000..3a7134e
--- /dev/null
+++ b/config/services.yml
@@ -0,0 +1,10 @@
+services:
+ PrestaShop\Module\DashActivity\Controller\ConfigurationController:
+ public: true
+ autowire: true
+ autoconfigure: true
+
+ PrestaShop\Module\DashActivity\Type\ConfigurationType:
+ autowire: true
+ autoconfigure: true
+ tags: ['form.type']
diff --git a/dashactivity.php b/dashactivity.php
index 9e725fc..32a4b2f 100755
--- a/dashactivity.php
+++ b/dashactivity.php
@@ -23,6 +23,9 @@
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/
+use PrestaShop\PrestaShop\Adapter\SymfonyContainer;
+use Twig\Environment;
+
if (!defined('_PS_VERSION_')) {
exit;
}
@@ -35,7 +38,7 @@ public function __construct()
{
$this->name = 'dashactivity';
$this->tab = 'administration';
- $this->version = '2.1.2';
+ $this->version = '3.0.0';
$this->author = 'PrestaShop';
parent::__construct();
@@ -47,17 +50,61 @@ public function __construct()
public function install()
{
Configuration::updateValue('DASHACTIVITY_CART_ACTIVE', 30);
- Configuration::updateValue('DASHACTIVITY_CART_ABANDONED_MIN', 24);
- Configuration::updateValue('DASHACTIVITY_CART_ABANDONED_MAX', 48);
+ // In minutes (see hookDashboardData()'s strtotime('- X MIN')), not hours: 1440/2880 is 24h/48h.
+ Configuration::updateValue('DASHACTIVITY_CART_ABANDONED_MIN', 1440);
+ Configuration::updateValue('DASHACTIVITY_CART_ABANDONED_MAX', 2880);
Configuration::updateValue('DASHACTIVITY_VISITOR_ONLINE', 30);
return parent::install()
+ && $this->createConfigurationTab()
&& $this->registerHook('dashboardZoneOne')
&& $this->registerHook('dashboardData')
&& $this->registerHook('actionAdminControllerSetMedia')
+ // Modern counterpart of dashboardZoneOne, registered alongside it (#41971).
+ && $this->registerHook('displayAdminDashboardZoneOne')
;
}
+ public function uninstall()
+ {
+ $idTab = (int) Tab::getIdFromClassName('AdminDashactivityConfiguration');
+ if ($idTab) {
+ (new Tab($idTab))->delete();
+ }
+
+ return parent::uninstall();
+ }
+
+ /**
+ * Hidden tab (id_parent -1): only used to back the settings route's ACL. Shared between
+ * install() and the 3.0.0 upgrade script so shops upgrading from an earlier version get it too.
+ */
+ public function createConfigurationTab(): bool
+ {
+ if (Tab::getIdFromClassName('AdminDashactivityConfiguration')) {
+ return true;
+ }
+
+ $tab = new Tab();
+ $tab->active = true;
+ $tab->class_name = 'AdminDashactivityConfiguration';
+ $tab->name = [];
+ foreach (Language::getLanguages(true) as $lang) {
+ $tab->name[$lang['id_lang']] = 'Dashactivity configuration';
+ }
+ $tab->id_parent = -1;
+ $tab->module = $this->name;
+
+ return $tab->add();
+ }
+
+ public function getContent()
+ {
+ Tools::redirectAdmin(
+ SymfonyContainer::getInstance()->get('router')->generate('dashactivity_configuration')
+ );
+ }
+
public function hookActionAdminControllerSetMedia()
{
if (get_class($this->context->controller) == 'AdminDashboardController') {
@@ -80,6 +127,102 @@ public function hookDashboardZoneOne($params)
return $this->display(__FILE__, 'dashboard_zone_one.tpl');
}
+ /**
+ * Modern counterpart of hookDashboardZoneOne(). Reuses hookDashboardData() as-is.
+ */
+ public function hookDisplayAdminDashboardZoneOne(array $params)
+ {
+ $data = $this->hookDashboardData($params);
+
+ return $this->render('zone_one.html.twig', [
+ 'kpiTitle' => $this->trans('Activity overview', [], 'Modules.Dashactivity.Admin'),
+ 'kpiItems' => $this->getKpiList($data['data_value']),
+ 'configUrl' => $this->getConfigUrl(),
+ 'chartTitle' => $this->trans('Traffic Sources', [], 'Modules.Dashactivity.Admin'),
+ 'chartId' => 'dashactivity-traffic-sources',
+ 'chartConfig' => $this->getTrafficSourcesChartConfig($data['data_chart']['dash_trends_chart1'] ?? []),
+ ]);
+ }
+
+ private function render(string $template, array $params = []): string
+ {
+ $twig = $this->get('twig');
+ if (!$twig instanceof Environment) {
+ return '';
+ }
+
+ return $twig->render('@Modules/dashactivity/views/templates/admin/' . $template, $params);
+ }
+
+ /**
+ * Null (no "Configure" link shown) when the current employee can't configure this module.
+ */
+ private function getConfigUrl(): ?string
+ {
+ if (!Tab::checkTabRights((int) Tab::getIdFromClassName('AdminDashactivityConfiguration'))) {
+ return null;
+ }
+
+ return SymfonyContainer::getInstance()->get('router')->generate('dashactivity_configuration');
+ }
+
+ /**
+ * Plain label/value pairs — no JS contract needed, this is server-rendered HTML.
+ */
+ private function getKpiList(array $dataValue): array
+ {
+ $labels = [
+ 'pending_orders' => $this->trans('Orders', [], 'Admin.Global'),
+ 'return_exchanges' => $this->trans('Return/Exchanges', [], 'Modules.Dashactivity.Admin'),
+ 'abandoned_cart' => $this->trans('Abandoned Carts', [], 'Admin.Global'),
+ 'products_out_of_stock' => $this->trans('Out of Stock Products', [], 'Modules.Dashactivity.Admin'),
+ 'new_messages' => $this->trans('New Messages', [], 'Modules.Dashactivity.Admin'),
+ 'product_reviews' => $this->trans('Product Reviews', [], 'Modules.Dashactivity.Admin'),
+ 'new_customers' => $this->trans('New Customers', [], 'Modules.Dashactivity.Admin'),
+ 'online_visitor' => $this->trans('Online Visitors', [], 'Modules.Dashactivity.Admin'),
+ 'active_shopping_cart' => $this->trans('Active Shopping Carts', [], 'Modules.Dashactivity.Admin'),
+ 'new_registrations' => $this->trans('New Subscriptions', [], 'Modules.Dashactivity.Admin'),
+ 'total_suscribers' => $this->trans('Total Subscribers', [], 'Modules.Dashactivity.Admin'),
+ 'visits' => $this->trans('Visits', [], 'Modules.Dashactivity.Admin'),
+ 'unique_visitors' => $this->trans('Unique Visitors', [], 'Modules.Dashactivity.Admin'),
+ ];
+
+ $kpis = [];
+ foreach ($labels as $key => $label) {
+ if (array_key_exists($key, $dataValue)) {
+ $kpis[] = ['label' => $label, 'value' => $dataValue[$key]];
+ }
+ }
+
+ return $kpis;
+ }
+
+ /**
+ * Plain Chart.js config, no dataset color (auto-applied by the core psColors plugin).
+ */
+ private function getTrafficSourcesChartConfig(array $nvd3Chart): array
+ {
+ $labels = [];
+ $values = [];
+ foreach ($nvd3Chart['data'] ?? [] as $point) {
+ $labels[] = $point['key'];
+ $values[] = $point['y'];
+ }
+
+ return [
+ 'type' => 'doughnut',
+ 'data' => [
+ 'labels' => $labels,
+ 'datasets' => [
+ ['data' => $values],
+ ],
+ ],
+ 'options' => [
+ 'plugins' => ['legend' => ['position' => 'bottom']],
+ ],
+ ];
+ }
+
public function hookDashboardData($params)
{
if (Tools::strlen($params['date_from']) == 10) {
@@ -398,17 +541,17 @@ public function renderConfigForm()
];
$fields_form['form']['input'][] = [
'label' => $this->trans('Abandoned cart (min)', [], 'Modules.Dashactivity.Admin'),
- 'hint' => $this->trans('How long (in hours) after the last action a cart is to be considered as abandoned (default: 24 hrs).', [], 'Modules.Dashactivity.Admin'),
+ 'hint' => $this->trans('How long (in minutes) after the last action a cart is to be considered as abandoned (default: 1440 min, 24h).', [], 'Modules.Dashactivity.Admin'),
'name' => 'DASHACTIVITY_CART_ABANDONED_MIN',
'type' => 'text',
- 'suffix' => $this->trans('hrs', [], 'Modules.Dashactivity.Admin'),
+ 'suffix' => $this->trans('min', [], 'Modules.Dashactivity.Admin'),
];
$fields_form['form']['input'][] = [
'label' => $this->trans('Abandoned cart (max)', [], 'Modules.Dashactivity.Admin'),
- 'hint' => $this->trans('How long (in hours) after the last action a cart is no longer to be considered as abandoned (default: 24 hrs).', [], 'Modules.Dashactivity.Admin'),
+ 'hint' => $this->trans('How long (in minutes) after the last action a cart is no longer to be considered as abandoned (default: 2880 min, 48h).', [], 'Modules.Dashactivity.Admin'),
'name' => 'DASHACTIVITY_CART_ABANDONED_MAX',
'type' => 'text',
- 'suffix' => $this->trans('hrs', [], 'Modules.Dashactivity.Admin'),
+ 'suffix' => $this->trans('min', [], 'Modules.Dashactivity.Admin'),
];
$helper = new HelperForm();
diff --git a/src/Controller/ConfigurationController.php b/src/Controller/ConfigurationController.php
new file mode 100644
index 0000000..8b2e5b7
--- /dev/null
+++ b/src/Controller/ConfigurationController.php
@@ -0,0 +1,92 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+
+declare(strict_types=1);
+
+namespace PrestaShop\Module\DashActivity\Controller;
+
+use Configuration;
+use PrestaShop\Module\DashActivity\Type\ConfigurationType;
+// FrameworkBundleAdminController is deprecated since 9.0 in favor of PrestaShopAdminController,
+// which doesn't exist on PS 8.2 — kept for the module's declared 8.2+ compatibility.
+use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController;
+use PrestaShopBundle\Security\Annotation\AdminSecurity;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+class ConfigurationController extends FrameworkBundleAdminController
+{
+ private const FIELDS = [
+ 'DASHACTIVITY_CART_ACTIVE',
+ 'DASHACTIVITY_VISITOR_ONLINE',
+ 'DASHACTIVITY_CART_ABANDONED_MIN',
+ 'DASHACTIVITY_CART_ABANDONED_MAX',
+ ];
+
+ // Every install() before 3.0.0 set these two exact values, meant as hours (the free-text
+ // field's own former label) though always read back as minutes — never anything else, since
+ // no UI let a merchant change them before this settings page existed.
+ private const LEGACY_ABANDONED_CART_MIN_DEFAULT = 24;
+ private const LEGACY_ABANDONED_CART_MAX_DEFAULT = 48;
+
+ /**
+ * @AdminSecurity("is_granted('read', request.get('_legacy_controller'))")
+ */
+ public function indexAction(Request $request): Response
+ {
+ $data = [];
+ foreach (self::FIELDS as $field) {
+ $data[$field] = (int) Configuration::get($field);
+ }
+
+ // Correct the known pre-3.0.0 default to what it always meant (1440 min = 24h, 2880 min
+ // = 48h) for display only: nothing is written back unless the merchant saves the form.
+ if (self::LEGACY_ABANDONED_CART_MIN_DEFAULT === $data['DASHACTIVITY_CART_ABANDONED_MIN']) {
+ $data['DASHACTIVITY_CART_ABANDONED_MIN'] = 1440;
+ }
+ if (self::LEGACY_ABANDONED_CART_MAX_DEFAULT === $data['DASHACTIVITY_CART_ABANDONED_MAX']) {
+ $data['DASHACTIVITY_CART_ABANDONED_MAX'] = 2880;
+ }
+
+ $form = $this->createForm(ConfigurationType::class, $data);
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+ $this->denyAccessUnlessGranted('update', $request->attributes->get('_legacy_controller'));
+
+ // No @DemoRestricted attribute here: its listener only recognizes the PHP 8
+ // attribute form (PrestaShopBundle\Security\Attribute\DemoRestricted), which
+ // doesn't exist on PS 8.2 — this module's declared minimum version.
+ if ($this->isDemoModeEnabled()) {
+ $this->addFlash('error', $this->trans('This functionality has been disabled.', 'Admin.Notifications.Error'));
+
+ return $this->redirectToRoute('dashactivity_configuration');
+ }
+
+ foreach ($form->getData() as $field => $value) {
+ Configuration::updateValue($field, (int) $value);
+ }
+ $this->addFlash('success', $this->trans('Successful update.', 'Admin.Notifications.Success'));
+
+ return $this->redirectToRoute('dashactivity_configuration');
+ }
+
+ return $this->render('@Modules/dashactivity/views/templates/admin/configuration.html.twig', [
+ 'configurationForm' => $form->createView(),
+ 'enableSidebar' => true,
+ 'help_link' => $this->generateSidebarLink('AdminDashactivityConfiguration'),
+ ]);
+ }
+}
diff --git a/src/Controller/index.php b/src/Controller/index.php
new file mode 100644
index 0000000..45df26c
--- /dev/null
+++ b/src/Controller/index.php
@@ -0,0 +1,34 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/src/Type/ConfigurationType.php b/src/Type/ConfigurationType.php
new file mode 100644
index 0000000..89319f3
--- /dev/null
+++ b/src/Type/ConfigurationType.php
@@ -0,0 +1,103 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+
+declare(strict_types=1);
+
+namespace PrestaShop\Module\DashActivity\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * Plain AbstractType (no need for TranslatorAwareType's locales), with TranslatorInterface
+ * constructor-injected directly so label/help strings go through an explicit trans() call —
+ * required for the translation extractor to pick them up, since it only extracts ChoiceType
+ * "choices", not "label"/"help" option strings.
+ */
+class ConfigurationType extends AbstractType
+{
+ private const DELAY_CHOICES = [15, 30, 45, 60, 90, 120];
+
+ // DASHACTIVITY_CART_ABANDONED_MIN/MAX are consumed as minutes (strtotime('- X MIN') in
+ // dashactivity.php), not hours as the field used to suggest — 30 min to 3 days covers the
+ // range a store would realistically want for a cart abandonment window.
+ private const ABANDONED_CART_CHOICES = [30, 60, 120, 240, 360, 720, 1440, 2880, 4320];
+
+ private const DOMAIN = 'Modules.Dashactivity.Admin';
+
+ /** @var TranslatorInterface */
+ private $translator;
+
+ public function __construct(TranslatorInterface $translator)
+ {
+ $this->translator = $translator;
+ }
+
+ public function buildForm(FormBuilderInterface $builder, array $options): void
+ {
+ $delayChoices = [];
+ foreach (self::DELAY_CHOICES as $minutes) {
+ $delayChoices[self::formatMinutesChoiceLabel($minutes)] = $minutes;
+ }
+ $abandonedCartChoices = [];
+ foreach (self::ABANDONED_CART_CHOICES as $minutes) {
+ $abandonedCartChoices[self::formatMinutesChoiceLabel($minutes)] = $minutes;
+ }
+
+ $builder
+ ->add('DASHACTIVITY_CART_ACTIVE', ChoiceType::class, [
+ 'label' => $this->translator->trans('Active cart', [], self::DOMAIN),
+ 'help' => $this->translator->trans('How long (in minutes) a cart is to be considered as active after the last recorded change (default: 30 min).', [], self::DOMAIN),
+ 'choices' => $delayChoices,
+ ])
+ ->add('DASHACTIVITY_VISITOR_ONLINE', ChoiceType::class, [
+ 'label' => $this->translator->trans('Online visitor', [], self::DOMAIN),
+ 'help' => $this->translator->trans('How long (in minutes) a visitor is to be considered as online after their last action (default: 30 min).', [], self::DOMAIN),
+ 'choices' => $delayChoices,
+ ])
+ ->add('DASHACTIVITY_CART_ABANDONED_MIN', ChoiceType::class, [
+ 'label' => $this->translator->trans('Abandoned cart (min)', [], self::DOMAIN),
+ 'help' => $this->translator->trans('How long (in minutes) after the last action a cart is to be considered as abandoned (default: 1440 min, 24h).', [], self::DOMAIN),
+ 'choices' => $abandonedCartChoices,
+ ])
+ ->add('DASHACTIVITY_CART_ABANDONED_MAX', ChoiceType::class, [
+ 'label' => $this->translator->trans('Abandoned cart (max)', [], self::DOMAIN),
+ 'help' => $this->translator->trans('How long (in minutes) after the last action a cart is no longer to be considered as abandoned (default: 2880 min, 48h).', [], self::DOMAIN),
+ 'choices' => $abandonedCartChoices,
+ ])
+ ;
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults([
+ 'required' => true,
+ 'translation_domain' => 'Modules.Dashactivity.Admin',
+ ]);
+ }
+
+ // e.g. 1440 -> "24h", 30 -> "30 min".
+ private static function formatMinutesChoiceLabel(int $minutes): string
+ {
+ if ($minutes >= 60 && 0 === $minutes % 60) {
+ return sprintf('%dh', intdiv($minutes, 60));
+ }
+
+ return sprintf('%d min', $minutes);
+ }
+}
diff --git a/src/Type/index.php b/src/Type/index.php
new file mode 100644
index 0000000..45df26c
--- /dev/null
+++ b/src/Type/index.php
@@ -0,0 +1,34 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/src/index.php b/src/index.php
new file mode 100644
index 0000000..45df26c
--- /dev/null
+++ b/src/index.php
@@ -0,0 +1,34 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/tests/php/phpstan/phpstan.neon b/tests/php/phpstan/phpstan.neon
index 018ab98..0cb7f98 100644
--- a/tests/php/phpstan/phpstan.neon
+++ b/tests/php/phpstan/phpstan.neon
@@ -1,2 +1,10 @@
# Base PHPStan configuration for the module
- # This file is included by version-specific configs
\ No newline at end of file
+ # This file is included by version-specific configs
+
+parameters:
+ # Reset to auto-detection: the shared CI action injects a phpVersion computed from the
+ # matrix's php-version input, which some older PrestaShop releases' pinned PHPStan can't
+ # parse once that input reaches a version newer than its own release date (PHPStan's
+ # phpVersion schema is capped at whatever PHP existed when that PHPStan version shipped).
+ # Included last, so this wins over the injected value (see PrestaShop/.github#phpstan).
+ phpVersion: null
diff --git a/upgrade/index.php b/upgrade/index.php
new file mode 100644
index 0000000..45df26c
--- /dev/null
+++ b/upgrade/index.php
@@ -0,0 +1,34 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/upgrade/upgrade-3.0.0.php b/upgrade/upgrade-3.0.0.php
new file mode 100644
index 0000000..53c1010
--- /dev/null
+++ b/upgrade/upgrade-3.0.0.php
@@ -0,0 +1,34 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+if (!defined('_PS_VERSION_')) {
+ exit;
+}
+
+function upgrade_module_3_0_0($object)
+{
+ return $object->createConfigurationTab()
+ && $object->registerHook('displayAdminDashboardZoneOne');
+}
diff --git a/views/templates/admin/configuration.html.twig b/views/templates/admin/configuration.html.twig
new file mode 100644
index 0000000..cc3006e
--- /dev/null
+++ b/views/templates/admin/configuration.html.twig
@@ -0,0 +1,31 @@
+{#**
+ * Copyright since 2007 PrestaShop SA and Contributors
+ * PrestaShop is an International Registered Trademark & Property of PrestaShop SA
+ *
+ * This source file is subject to the Academic Free License 3.0 (AFL-3.0).
+ * It is also available through the world-wide-web at this URL: https://opensource.org/licenses/AFL-3.0
+ *#}
+{% extends '@PrestaShop/Admin/layout.html.twig' %}
+
+{% block content %}
+ {{ form_start(configurationForm) }}
+
+
+
+
+
+ {{ form_widget(configurationForm) }}
+
+
+
+
+
+ {{ form_end(configurationForm) }}
+{% endblock %}
diff --git a/views/templates/admin/index.php b/views/templates/admin/index.php
new file mode 100644
index 0000000..45df26c
--- /dev/null
+++ b/views/templates/admin/index.php
@@ -0,0 +1,34 @@
+
+ * @copyright Since 2007 PrestaShop SA and Contributors
+ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
+ */
+header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
+
+header('Cache-Control: no-store, no-cache, must-revalidate');
+header('Cache-Control: post-check=0, pre-check=0', false);
+header('Pragma: no-cache');
+
+header('Location: ../');
+exit;
diff --git a/views/templates/admin/zone_one.html.twig b/views/templates/admin/zone_one.html.twig
new file mode 100644
index 0000000..c8860fd
--- /dev/null
+++ b/views/templates/admin/zone_one.html.twig
@@ -0,0 +1,26 @@
+{#
+ # For the full copyright and license information, please view the
+ # docs/licenses/LICENSE.txt file that was distributed with this source code.
+ #}
+
+ {% component 'Card' with {title: kpiTitle, icon: 'insights', configUrl: configUrl} %}
+ {% block content %}
+
+ {% for item in kpiItems %}
+ -
+ {{ item.label }}
+ {{ item.value }}
+
+ {% endfor %}
+
+ {% endblock %}
+ {% endcomponent %}
+
+
+{{ component('ChartCard', {
+ chartId: chartId,
+ config: chartConfig,
+ height: 280,
+ title: chartTitle,
+ icon: 'donut_small',
+}) }}