From 86d76ea4e86768f961e58e8a44d2ede67de9fd38 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 2 Nov 2023 12:48:49 +0530 Subject: [PATCH] Updated: KPI design at back office --- admin/themes/default/sass/partials/_kpi.sass | 187 +++++++-- .../default/template/helpers/kpi/kpi.tpl | 34 +- .../default/template/helpers/kpi/row.tpl | 50 ++- classes/controller/AdminController.php | 12 + classes/helper/HelperKpi.php | 4 +- classes/helper/HelperKpiRow.php | 14 + controllers/admin/AdminCartsController.php | 8 +- .../AdminCustomerPreferencesController.php | 38 +- .../admin/AdminCustomerThreadsController.php | 6 +- .../admin/AdminCustomersController.php | 110 +++-- controllers/admin/AdminModulesController.php | 6 +- .../admin/AdminPPreferencesController.php | 21 +- controllers/admin/AdminProductsController.php | 134 ++++--- controllers/admin/AdminStatsController.php | 378 +++++++++++++++--- .../admin/AdminTranslationsController.php | 6 +- install/data/xml/configuration.xml | 15 + js/admin.js | 48 +++ 17 files changed, 856 insertions(+), 215 deletions(-) diff --git a/admin/themes/default/sass/partials/_kpi.sass b/admin/themes/default/sass/partials/_kpi.sass index 3d2605967..a1d01eb1e 100644 --- a/admin/themes/default/sass/partials/_kpi.sass +++ b/admin/themes/default/sass/partials/_kpi.sass @@ -1,14 +1,85 @@ +$kpi-rows-gap: 8px +$kpi-gap: 10px + .kpi-container - padding-bottom: 10px !important - .kpi-refresh - position: absolute - z-index: 1 - right: 20px + display: flex + justify-content: space-between + padding: 12px !important + .kpis-wrap + width: 100% + white-space: nowrap + overflow-x: auto + font-size: 0 + margin-bottom: -$kpi-rows-gap + display: flex + flex-wrap: wrap + margin-left: -($kpi-gap / 2) + margin-right: -($kpi-gap / 2) + .kpi-wrap + display: inline-block + width: 20% + margin-bottom: $kpi-rows-gap + padding-left: $kpi-gap / 2 + padding-right: $kpi-gap / 2 + .actions-wrap + width: 16px + margin-left: 16px + button + padding: 0 + border: none + opacity: 0.2 + display: block + width: 100% + &:not(:first-of-type) + margin-top: 4px + &:hover + opacity: 0.5 + background-color: #fff + &:focus, + &:active + outline: none !important + i + color: #000 !important + font-size: 16px + &.icon-ellipsis-vertical + position: relative + top: 3px + .dropdown-menu + top: 25px + right: 0 + left: unset + min-width: 200px + font-size: 13px + padding: 8px 12px + label + font-weight: normal + &.no-wrapping + .kpis-wrap + white-space: normal + flex-wrap: nowrap + .kpi-wrap + min-width: 200px + +@media (max-width: $screen-desktop) + .kpi-container .kpis-wrap .kpi-wrap + flex-grow: 1 + &.no-wrapping + .kpis-wrap + flex-wrap: nowrap !important + +@media (max-width: $screen-lg) + .kpi-container .kpis-wrap .kpi-wrap + width: 20% +@media (max-width: $screen-sm) + .kpi-container .kpis-wrap .kpi-wrap + width: 33.33% +@media (max-width: $screen-xs) + .kpi-container .kpis-wrap .kpi-wrap + width: 50% .box-stats display: block - height: 64px - margin-bottom: 10px + height: 100% .boxchart-overlay @include padding(10px, 10px, 5px, 10px) @include margin-right(10px) @@ -16,58 +87,86 @@ @include border-radius(3px) .kpi-content width: 100% + height: 100% position: relative - @include padding-left(40px) - @include float(left) - i + border: 1px solid #e7edfa + border-left-width: 3px + border-radius: 6px + padding: 4px 8px 8px 42px + display: flex + flex-direction: column + justify-content: space-between + white-space: normal + .title-subtitle + display: flex + justify-content: space-between + .title + display: block + color: #666 + font-size: 13px + i + position: absolute + top: 5px + font-size: 15px + text-align: center + color: white + line-height: 26px + background: #eee + width: 28px + height: 28px + @include left(5px) + @include border-radius(6px) + &::before + position: absolute + top: 1px + left: 8px + .subtitle + display: block + text-transform: capitalize + color: #aaa + font-size: 10px + position: relative + top: 3px + text-align: right + .value-container + .value + display: block + clear: both + font-size: 14px + font-weight: bold + color: #666 + min-height: 22px + &.loading + height: 22px + min-width: 40px + .arrow position: absolute - top: 0 - font-size: 26px - text-align: center - color: white - line-height: 26px - @include left(0) - @include border-radius(3px) - - .title - display: block - color: #666 - .subtitle - display: block - text-transform: uppercase - color: #aaa - .value - display: block - clear: both - font-size: 1.5em - white-space: nowrap - text-overflow: ellipsis - overflow: hidden - &.loading - height: 1.2em - width: 200px + bottom: 7px + right: 9px + i + color: #828282 !important &:hover text-decoration: none .color1 + .kpi-content + border-left-color: #2BA8E3 i, .boxchart-overlay color: #2BA8E3 !important - .value - color: #2BA8E3 .color2 + .kpi-content + border-left-color: #FF5450 i, .boxchart-overlay color: #FF5450 !important - .value - color: #FF5450 .color3 + .kpi-content + border-left-color: #9E5BA1 i, .boxchart-overlay color: #9E5BA1 !important - .value - color: #9E5BA1 .color4 + .kpi-content + border-left-color: #95CC6B i, .boxchart-overlay color: #95CC6B !important - .value - color: #95CC6B .data_chart rect @@ -80,4 +179,4 @@ stroke: black .area fill: rgba(white,0.30) - stroke-width: 0 + stroke-width: 0 \ No newline at end of file diff --git a/admin/themes/default/template/helpers/kpi/kpi.tpl b/admin/themes/default/template/helpers/kpi/kpi.tpl index e29ae28c4..5abf38353 100644 --- a/admin/themes/default/template/helpers/kpi/kpi.tpl +++ b/admin/themes/default/template/helpers/kpi/kpi.tpl @@ -25,21 +25,37 @@ <{if isset($href) && $href}a style="display:block" href="{$href|escape:'html':'UTF-8'}"{else}div{/if} id="{$id|escape:'html':'UTF-8'}" data-toggle="tooltip" class="box-stats label-tooltip {$color|escape}" data-original-title="{$tooltip|escape}">
- {if isset($icon) && $icon} - - {/if} - {$title|escape} - {$subtitle|escape} +
+
+ + {if isset($icon) && $icon}{/if} + {$title|escape} + +
+ {if $subtitle} +
+ + {$subtitle|escape} + +
+ {/if} +
{if isset($chart) && $chart}
{/if} - {if isset($source) && $source} - - {elseif isset($value) && $value != ''} - {$value|escape:'html':'UTF-8'} +
+ {if isset($source) && $source} + + {elseif isset($value) && $value != ''} + {$value|escape:'html':'UTF-8'} + {/if} +
+ + {if isset($href) && $href} + {/if}
diff --git a/admin/themes/default/template/helpers/kpi/row.tpl b/admin/themes/default/template/helpers/kpi/row.tpl index fc481cbd3..4ee239109 100644 --- a/admin/themes/default/template/helpers/kpi/row.tpl +++ b/admin/themes/default/template/helpers/kpi/row.tpl @@ -22,20 +22,42 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) * International Registered Trademark & Property of PrestaShop SA *} -
- {if $refresh} -
- {/if} - {if ($kpis|count) > 0} -
- {assign var='col' value=(int)(12 / $kpis|count)} - {foreach from=$kpis item=i name=kpi} - {if $smarty.foreach.kpi.iteration > $col+1} -
-
- {/if} -
{$i}
+ +
+
+ {foreach from=$kpis item=kpi} +
visible) && !$kpi->visible}style="display: none;"{/if}> + {$kpi->generate()} +
{/foreach}
- {/if} + +
+ {if $refresh} + + {/if} + + + + +
diff --git a/classes/controller/AdminController.php b/classes/controller/AdminController.php index ea3a469f7..f41e93ff7 100644 --- a/classes/controller/AdminController.php +++ b/classes/controller/AdminController.php @@ -963,6 +963,18 @@ class AdminControllerCore extends Controller return true; } + protected function ajaxProcessChangeKpiVisibility() + { + $cookieKey = 'kpi_visibility_'.$this->context->controller->className.'_'.Tools::getValue('kpi_id'); + $this->context->cookie->$cookieKey = (int) Tools::getValue('is_visible'); + } + + protected function ajaxProcessSaveKpiView() + { + $cookieKey = 'kpi_wrapping_'.$this->context->controller->className; + $this->context->cookie->$cookieKey = (int) Tools::getValue('no_wrapping'); + } + /** * @TODO uses redirectAdmin only if !$this->ajax * @return bool diff --git a/classes/helper/HelperKpi.php b/classes/helper/HelperKpi.php index a1b54a474..342de1bb7 100644 --- a/classes/helper/HelperKpi.php +++ b/classes/helper/HelperKpi.php @@ -40,6 +40,7 @@ class HelperKpiCore extends Helper public $source; public $href; public $tooltip; + public $visible; public function generate() { @@ -56,7 +57,8 @@ class HelperKpiCore extends Helper 'data' => $this->data, 'source' => $this->source, 'href' => $this->href, - 'tooltip' => $this->tooltip + 'tooltip' => $this->tooltip, + 'visible' => $this->visible, )); return $this->tpl->fetch(); diff --git a/classes/helper/HelperKpiRow.php b/classes/helper/HelperKpiRow.php index 7f1d4e594..b6daea61a 100644 --- a/classes/helper/HelperKpiRow.php +++ b/classes/helper/HelperKpiRow.php @@ -36,8 +36,22 @@ class HelperKpiRowCore extends Helper { $this->tpl = $this->createTemplate($this->base_tpl); + // set visiblity for each KPI + $cookieKeyPrefix = 'kpi_visibility_'.$this->context->controller->className.'_'; + foreach ($this->kpis as &$kpi) { + $cookieKey = $cookieKeyPrefix.$kpi->id; + if (isset($this->context->cookie->$cookieKey)) { + $kpi->visible = (bool) $this->context->cookie->$cookieKey; + } else { + $kpi->visible = true; + } + } + + $cookieKeyView = 'kpi_wrapping_'.$this->context->controller->className; + $this->tpl->assign('kpis', $this->kpis); $this->tpl->assign('refresh', $this->refresh); + $this->tpl->assign('no_wrapping', (int) $this->context->cookie->$cookieKeyView); return $this->tpl->fetch(); } diff --git a/controllers/admin/AdminCartsController.php b/controllers/admin/AdminCartsController.php index 100489af1..f284cd978 100644 --- a/controllers/admin/AdminCartsController.php +++ b/controllers/admin/AdminCartsController.php @@ -172,7 +172,7 @@ class AdminCartsControllerCore extends AdminController $helper->data = ConfigurationKPI::get('CONVERSION_RATE_CHART'); } $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=conversion_rate'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-carts'; @@ -184,7 +184,7 @@ class AdminCartsControllerCore extends AdminController $helper->subtitle = sprintf($this->l('From %s to %s', null, null, false), $date_from, $date_to); $helper->href = $this->context->link->getAdminLink('AdminCarts').'&action=filterOnlyAbandonedCarts'; $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=abandoned_cart'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-average-order'; @@ -193,7 +193,7 @@ class AdminCartsControllerCore extends AdminController $helper->title = $this->l('Average Order Value', null, null, false); $helper->subtitle = $this->l('30 days', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=average_order_value'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-net-profit-visitor'; @@ -202,7 +202,7 @@ class AdminCartsControllerCore extends AdminController $helper->title = $this->l('Net Profit per Visitor', null, null, false); $helper->subtitle = $this->l('30 days', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=netprofit_visit'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/controllers/admin/AdminCustomerPreferencesController.php b/controllers/admin/AdminCustomerPreferencesController.php index 00c2a9e68..1050c0772 100644 --- a/controllers/admin/AdminCustomerPreferencesController.php +++ b/controllers/admin/AdminCustomerPreferencesController.php @@ -90,7 +90,8 @@ class AdminCustomerPreferencesControllerCore extends AdminController 'cast' => 'intval', 'size' => 5, 'type' => 'text', - 'suffix' => $this->l('minutes') + 'suffix' => $this->l('minutes'), + 'class' => 'fixed-width-xl', ), 'PS_B2B_ENABLE' => array( 'title' => $this->l('Enable B2B mode'), @@ -120,6 +121,41 @@ class AdminCustomerPreferencesControllerCore extends AdminController 'cast' => 'intval', 'type' => 'bool' ), + 'PS_KPI_FREQUENT_CUSTOMER_NB_ORDERS' => array( + 'title' => $this->l('Number of orders to use to calculate frequent customers'), + 'hint' => $this->l('Set the number of orders to use to calculate frequent customers.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'class' => 'fixed-width-xxl', + ), + 'PS_KPI_REVPAC_NB_DAYS' => array( + 'title' => $this->l('Number of days to use to calculate RevPAC'), + 'hint' => $this->l('Set the number of days to use to calculate RevPAC.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'suffix' => $this->l('days'), + 'class' => 'fixed-width-xl', + ), + 'PS_KPI_CONVERSION_RATE_NB_DAYS' => array( + 'title' => $this->l('Number of days to use to calculate Conversion Rate'), + 'hint' => $this->l('Set the number of days to use to calculate Conversion Rate.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'suffix' => $this->l('days'), + 'class' => 'fixed-width-xl', + ), + 'PS_KPI_NEW_CUSTOMERS_NB_DAYS' => array( + 'title' => $this->l('Number of days to use to calculate New Customers'), + 'hint' => $this->l('Set the number of days to use to calculate New Customers.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'suffix' => $this->l('days'), + 'class' => 'fixed-width-xl', + ), ), 'submit' => array('title' => $this->l('Save')), ), diff --git a/controllers/admin/AdminCustomerThreadsController.php b/controllers/admin/AdminCustomerThreadsController.php index e9534093d..00e53b7ec 100644 --- a/controllers/admin/AdminCustomerThreadsController.php +++ b/controllers/admin/AdminCustomerThreadsController.php @@ -515,7 +515,7 @@ class AdminCustomerThreadsControllerCore extends AdminController $helper->color = 'color1'; $helper->title = $this->l('Pending Discussion Threads', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=pending_messages'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-age'; @@ -524,7 +524,7 @@ class AdminCustomerThreadsControllerCore extends AdminController $helper->title = $this->l('Average Response Time', null, null, false); $helper->subtitle = $this->l('30 days', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=avg_msg_response_time'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-messages-per-thread'; @@ -533,7 +533,7 @@ class AdminCustomerThreadsControllerCore extends AdminController $helper->title = $this->l('Messages per Thread', null, null, false); $helper->subtitle = $this->l('30 day', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=messages_per_thread'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/controllers/admin/AdminCustomersController.php b/controllers/admin/AdminCustomersController.php index b7078a0f1..eed08c47d 100644 --- a/controllers/admin/AdminCustomersController.php +++ b/controllers/admin/AdminCustomersController.php @@ -643,41 +643,101 @@ class AdminCustomersControllerCore extends AdminController $time = time(); $kpis = array(); - $helper = new HelperKpi(); - $helper->id = 'box-gender'; - $helper->icon = 'icon-male'; - $helper->color = 'color1'; - $helper->title = $this->l('Customers', null, null, false); - $helper->subtitle = $this->l('All Time', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=customer_main_gender'; - $kpis[] = $helper->generate(); - - $helper = new HelperKpi(); - $helper->id = 'box-age'; - $helper->icon = 'icon-calendar'; - $helper->color = 'color2'; - $helper->title = $this->l('Average Age', 'AdminTab', null, false); - $helper->subtitle = $this->l('All Time', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=avg_customer_age'; - $kpis[] = $helper->generate(); - $helper = new HelperKpi(); $helper->id = 'box-orders'; - $helper->icon = 'icon-retweet'; - $helper->color = 'color3'; + $helper->icon = 'icon-shopping-cart'; + $helper->color = 'color1'; $helper->title = $this->l('Orders per Customer', null, null, false); $helper->subtitle = $this->l('All Time', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=orders_per_customer'; - $kpis[] = $helper->generate(); + $helper->tooltip = $this->l('The average number of orders placed per customer in given period of time.', null, null, false); + $kpis[] = $helper; $helper = new HelperKpi(); - $helper->id = 'box-newsletter'; + $helper->id = 'box-total-frequent-customers'; + $helper->icon = 'icon-star'; + $helper->color = 'color2'; + $helper->title = $this->l('Total Frequent Customers', null, null, false); + $helper->subtitle = $this->l('All Time', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_frequent_customers'; + $helper->tooltip = $this->l('The total number of frequent customers in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-revenue-per-available-customer'; + $helper->icon = 'icon-dollar'; + $helper->color = 'color3'; + $helper->title = $this->l('RevPAC', null, null, false); + $nbDaysRevPac = 30; + if (Configuration::get('PS_KPI_REVPAC_NB_DAYS') !== false) { + $nbDaysRevPac = Configuration::get('PS_KPI_REVPAC_NB_DAYS'); + } + $helper->subtitle = sprintf($this->l('%d Days', null, null, false), (int) $nbDaysRevPac); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=revenue_per_available_customer'; + $helper->tooltip = $this->l('Revenue per Available Customer (RevPAC) in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-newsletter-registrations'; $helper->icon = 'icon-envelope'; $helper->color = 'color4'; - $helper->title = $this->l('Newsletter Registrations', null, null, false); + $helper->title = $this->l('Total Newsletter Registrations', null, null, false); $helper->subtitle = $this->l('All Time', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=newsletter_registrations'; - $kpis[] = $helper->generate(); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_newsletter_registrations'; + $helper->tooltip = $this->l('The total number of newsletter registrations in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-conversion-rate'; + $helper->icon = 'icon-refresh'; + $helper->color = 'color1'; + $helper->title = $this->l('Conversion Rate', null, null, false); + $nbDaysConversionRate = 30; + if (Configuration::get('PS_KPI_CONVERSION_RATE_NB_DAYS') !== false) { + $nbDaysConversionRate = Configuration::get('PS_KPI_CONVERSION_RATE_NB_DAYS'); + } + $helper->subtitle = sprintf($this->l('%d Days', null, null, false), (int) $nbDaysConversionRate); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=conversion_rate'; + $helper->tooltip = $this->l('The percentage of visitors who created a booking in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-new-customers'; + $helper->icon = 'icon-plus-circle'; + $helper->color = 'color4'; + $helper->title = $this->l('New Customers', null, null, false); + $nbDaysNewCustomers = 30; + if (Configuration::get('PS_KPI_NEW_CUSTOMERS_NB_DAYS') !== false) { + $nbDaysNewCustomers = Configuration::get('PS_KPI_NEW_CUSTOMERS_NB_DAYS'); + } + $helper->subtitle = sprintf($this->l('%d Days', null, null, false), (int) $nbDaysNewCustomers); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_new_customers'; + $helper->tooltip = $this->l('The total number of new customers who registered in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-banned-customers'; + $helper->icon = 'icon-ban'; + $helper->color = 'color2'; + $helper->title = $this->l('Banned Customers', null, null, false); + $helper->subtitle = $this->l('All Time', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_banned_customers'; + $helper->tooltip = $this->l('The total number of banned customers.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-gender'; + $helper->icon = 'icon-male'; + $helper->color = 'color3'; + $helper->title = $this->l('Customers', null, null, false); + $helper->subtitle = $this->l('All Time', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=customer_main_gender'; + $helper->tooltip = $this->l('The main gender from all the customers.', null, null, false); + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/controllers/admin/AdminModulesController.php b/controllers/admin/AdminModulesController.php index 0eae6b275..9869ded51 100644 --- a/controllers/admin/AdminModulesController.php +++ b/controllers/admin/AdminModulesController.php @@ -1329,7 +1329,7 @@ class AdminModulesControllerCore extends AdminController $helper->color = 'color1'; $helper->title = $this->l('Installed Modules', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=installed_modules'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-disabled-modules'; @@ -1337,7 +1337,7 @@ class AdminModulesControllerCore extends AdminController $helper->color = 'color2'; $helper->title = $this->l('Disabled Modules', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=disabled_modules'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-update-modules'; @@ -1345,7 +1345,7 @@ class AdminModulesControllerCore extends AdminController $helper->color = 'color3'; $helper->title = $this->l('Modules to Update', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=update_modules'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/controllers/admin/AdminPPreferencesController.php b/controllers/admin/AdminPPreferencesController.php index 1ea8b3f50..fefdb6add 100644 --- a/controllers/admin/AdminPPreferencesController.php +++ b/controllers/admin/AdminPPreferencesController.php @@ -66,7 +66,8 @@ class AdminPPreferencesControllerCore extends AdminController 'hint' => $this->l('The counting of days starts from room type creation date.'), 'validation' => 'isUnsignedInt', 'cast' => 'intval', - 'type' => 'text' + 'type' => 'text', + 'class' => 'fixed-width-xxl', ), /*'PS_CART_REDIRECT' => array( 'title' => $this->l('Redirect after adding product to cart'), @@ -88,6 +89,7 @@ class AdminPPreferencesControllerCore extends AdminController 'cast' => 'intval', 'type' => 'text', 'suffix' => $this->l('characters'), + 'class' => 'fixed-width-xl', ), /*'PS_QTY_DISCOUNT_ON_COMBINATION' => array( 'title' => $this->l('Quantity discounts based on'), @@ -109,7 +111,16 @@ class AdminPPreferencesControllerCore extends AdminController 'cast' => 'intval', 'required' => false, 'type' => 'bool' - ) + ), + 'PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS' => array( + 'title' => $this->l('Number of days to use to calculate best selling room type'), + 'hint' => $this->l('Set the number of days to use to calculate best selling room type.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'suffix' => $this->l('days'), + 'class' => 'fixed-width-xl', + ), ), 'submit' => array('title' => $this->l('Save')) ), @@ -205,7 +216,8 @@ class AdminPPreferencesControllerCore extends AdminController 'hint' => $this->l('Number of room types displayed per page. Default is 10.'), 'validation' => 'isUnsignedInt', 'cast' => 'intval', - 'type' => 'text' + 'type' => 'text', + 'class' => 'fixed-width-xxl', ), 'PS_PRODUCTS_ORDER_BY' => array( 'title' => $this->l('Default order by'), @@ -312,7 +324,8 @@ class AdminPPreferencesControllerCore extends AdminController 'hint' => $this->l('Number of hotel images displayed per page. Default is 9.'), 'validation' => 'isUnsignedInt', 'cast' => 'intval', - 'type' => 'text' + 'type' => 'text', + 'class' => 'fixed-width-xxl', ), ), 'submit' => array('title' => $this->l('Save')) diff --git a/controllers/admin/AdminProductsController.php b/controllers/admin/AdminProductsController.php index 542c4bdb6..af72c442b 100644 --- a/controllers/admin/AdminProductsController.php +++ b/controllers/admin/AdminProductsController.php @@ -2304,61 +2304,103 @@ class AdminProductsControllerCore extends AdminController $time = time(); $kpis = array(); - // if (Configuration::get('PS_STOCK_MANAGEMENT')) { - // $helper = new HelperKpi(); - // $helper->id = 'box-products-stock'; - // $helper->icon = 'icon-archive'; - // $helper->color = 'color1'; - // $helper->title = $this->l('Out of stock items', null, null, false); - // if (ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK') !== false) { - // $helper->value = ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK'); - // } - // $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=percent_product_out_of_stock'; - // $helper->tooltip = $this->l('X% of your room types for sale are out of stock.', null, null, false); - // $helper->refresh = (bool)(ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK_EXPIRE') < $time); - // $helper->href = Context::getContext()->link->getAdminLink('AdminProducts').'&productFilter_sav!quantity=0&productFilter_active=1&submitFilterproduct=1'; - // $kpis[] = $helper->generate(); - // } + $helper = new HelperKpi(); + $helper->id = 'box-best-selling-room-type'; + $helper->icon = 'icon-star'; + $helper->color = 'color1'; + $helper->title = $this->l('Best Selling', null, null, false); + $nbDaysBestSelling = 30; + if (Configuration::get('PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS') !== false) { + $nbDaysBestSelling = Configuration::get('PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS'); + } + $helper->subtitle = sprintf($this->l('%d Days', null, null, false), (int) $nbDaysBestSelling); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=best_selling_room_type'; + $helper->tooltip = $this->l('Displays the best selling room type based on the last 30 days of sales.', null, null, false); + $kpis[] = $helper; $helper = new HelperKpi(); - $helper->id = 'box-avg-gross-margin'; - $helper->icon = 'icon-tags'; + $helper->id = 'box-total-disbled-room-types'; + $helper->icon = 'icon-ban'; $helper->color = 'color2'; - $helper->title = $this->l('Average Gross Margin %', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=product_avg_gross_margin'; - $helper->tooltip = $this->l('Gross margin expressed in percentage assesses how cost-effectively you sell your room types / products. Out of $100, you will retain $X to cover profit and expenses.', null, null, false); - $kpis[] = $helper->generate(); - - $helper = new HelperKpi(); - $helper->id = 'box-8020-sales-catalog'; - $helper->icon = 'icon-beaker'; - $helper->color = 'color3'; - $helper->title = $this->l('Purchased References', null, null, false); - $helper->subtitle = $this->l('30 days', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=8020_sales_catalog'; - $helper->tooltip = $this->l('X% of your references have been purchased for the past 30 days.', null, null, false); - if (Module::isInstalled('statsbestproducts')) { - $helper->href = Context::getContext()->link->getAdminLink('AdminStats').'&module=statsbestproducts&datepickerFrom='.date('Y-m-d', strtotime('-30 days')).'&datepickerTo='.date('Y-m-d'); - } - $kpis[] = $helper->generate(); - - $helper = new HelperKpi(); - $helper->id = 'box-disabled-products'; - $helper->icon = 'icon-off'; - $helper->color = 'color4'; - $helper->href = $this->context->link->getAdminLink('AdminProducts'); $helper->title = $this->l('Disabled Room Types', null, null, false); - if (ConfigurationKPI::get('DISABLED_ROOM_TYPES') !== false) { - $helper->value = ConfigurationKPI::get('DISABLED_ROOM_TYPES'); - } $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=disabled_room_types'; - $helper->refresh = (bool)(ConfigurationKPI::get('DISABLED_ROOM_TYPES_EXPIRE') < $time); - $helper->tooltip = $this->l('X% of your room types are disabled and not visible to your customers', null, null, false); $helper->href = Context::getContext()->link->getAdminLink('AdminProducts').'&productFilter_sa!active=0&submitFilterproduct=1'; - $kpis[] = $helper->generate(); + $helper->tooltip = $this->l('The total number of room types that are currently disabled.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-rooms'; + $helper->icon = 'icon-bed'; + $helper->color = 'color3'; + $helper->title = $this->l('Total Rooms', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_rooms'; + $helper->tooltip = $this->l('The total number of rooms in all hotels.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-online-bookable-rooms'; + $helper->icon = 'icon-globe'; + $helper->color = 'color4'; + $helper->title = $this->l('Total Online Bookable Rooms', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_online_bookable_rooms'; + $helper->tooltip = $this->l('The total number of rooms that can be booked using website.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-offline-bookable-rooms'; + $helper->icon = 'icon-building'; + $helper->color = 'color1'; + $helper->title = $this->l('Total Offline Bookable Rooms', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_offline_bookable_rooms'; + $helper->tooltip = $this->l('The number of rooms that can be booked through offline channels (e.g., phone or in-person).', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-occupied-rooms'; + $helper->icon = 'icon-user'; + $helper->color = 'color1'; + $helper->title = $this->l('Total Occupied Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_occupied_rooms'; + $helper->tooltip = $this->l('The current count of rooms that are currently occupied by guests.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-vacant-rooms'; + $helper->icon = 'icon-check-empty'; + $helper->color = 'color3'; + $helper->title = $this->l('Total Vacant Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_vacant_rooms'; + $helper->tooltip = $this->l('The count of rooms that are currently unoccupied and available for booking.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-reserved-rooms'; + $helper->icon = 'icon-calendar'; + $helper->color = 'color4'; + $helper->title = $this->l('Total Reserved Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_reserved_rooms'; + $helper->tooltip = $this->l('The number of rooms that are currently reserved but not yet occupied.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-disabled-rooms'; + $helper->icon = 'icon-ban'; + $helper->color = 'color2'; + $helper->title = $this->l('Total Disabled Rooms', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_disabled_rooms'; + $helper->tooltip = $this->l('The number of rooms that are currently disabled.', null, null, false); + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); $helper = new HelperKpiRow(); $helper->kpis = $kpis; + return $helper->generate(); } diff --git a/controllers/admin/AdminStatsController.php b/controllers/admin/AdminStatsController.php index cdca3016d..0514f3d4d 100644 --- a/controllers/admin/AdminStatsController.php +++ b/controllers/admin/AdminStatsController.php @@ -176,22 +176,29 @@ class AdminStatsControllerCore extends AdminStatsTabController '.Shop::addSqlAssociation('category', 'c')); } - public static function getDisabledRoomTypes() + public static function getDisabledRoomTypes($idHotel = null) { return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' SELECT COUNT(*) FROM `'._DB_PREFIX_.'product` p + INNER JOIN `'._DB_PREFIX_.'htl_room_type` hrt + ON (hrt.`id_product` = p.`id_product`) '.Shop::addSqlAssociation('product', 'p').' - WHERE product_shop.active = 0 AND p.`booking_product` = 1'); + WHERE product_shop.active = 0 AND p.`booking_product` = 1'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hrt') : '')); } - public static function getTotalRoomTypes() + public static function getTotalRoomTypes($idHotel = null) { return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' SELECT COUNT(*) FROM `'._DB_PREFIX_.'product` p + INNER JOIN `'._DB_PREFIX_.'htl_room_type` hrt + ON (hrt.`id_product` = p.`id_product`) '.Shop::addSqlAssociation('product', 'p').' - WHERE p.`booking_product` = 1'); + WHERE p.`booking_product` = 1'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hrt') : '') + ); } public static function getDisabledProducts() @@ -412,16 +419,6 @@ class AdminStatsControllerCore extends AdminStatsTabController return $row; } - public static function getAverageCustomerAge() - { - $value = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' - SELECT AVG(DATEDIFF("'.date('Y-m-d').' 00:00:00", birthday)) - FROM `'._DB_PREFIX_.'customer` c - WHERE active = 1 - AND birthday IS NOT NULL AND birthday != "0000-00-00" '.Shop::addSqlRestriction()); - return round($value / 365); - } - public static function getPendingMessages() { return CustomerThread::getTotalCustomerThreads('status LIKE "%pending%" OR status = "open"'.Shop::addSqlRestriction()); @@ -558,6 +555,27 @@ class AdminStatsControllerCore extends AdminStatsTabController return $expenses; } + public static function getBestSellingRoomType($dateFrom, $dateTo, $idHotel = null) + { + $sql = 'SELECT p.`id_product`, + ( + SELECT IFNULL(SUM(ROUND((DATEDIFF(LEAST(hbd.`date_to`, "'.pSQL($dateTo).'"), GREATEST(hbd.`date_from`, "'.pSQL($dateFrom).'")) / DATEDIFF(hbd.`date_to`, hbd.`date_from`)) * hbd.`total_price_tax_excl`, 2)), 0) + FROM `'._DB_PREFIX_.'htl_booking_detail` hbd + LEFT JOIN `'._DB_PREFIX_.'orders` o + ON (o.`id_order` = hbd.`id_order`) + WHERE hbd.`id_product` = p.`id_product` AND o.`valid` = 1 + AND hbd.`date_to` > "'.pSQL($dateFrom).'" AND hbd.`date_from` < "'.pSQL($dateTo).'" + ) AS totalRevenue + FROM `'._DB_PREFIX_.'product` p + INNER JOIN `'._DB_PREFIX_.'htl_room_type` hrt + ON (hrt.`id_product` = p.`id_product`) + WHERE p.`active` = 1 AND p.`booking_product` = 1'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hrt') : '').' + ORDER BY totalRevenue DESC'; + + return Db::getInstance()->getValue($sql); + } + public function displayAjaxGetKpi() { $value = $this->getLatestKpiValue(Tools::getValue('kpi')); @@ -577,17 +595,20 @@ class AdminStatsControllerCore extends AdminStatsTabController $value = false; switch ($kpi) { case 'conversion_rate': - $visitors = AdminStatsController::getVisits(true, date('Y-m-d', strtotime('-31 day')), date('Y-m-d', strtotime('-1 day')), false /*'day'*/); - $orders = AdminStatsController::getOrders(date('Y-m-d', strtotime('-31 day')), date('Y-m-d', strtotime('-1 day')), false /*'day'*/); + $nbDaysConversionRate = Configuration::get('PS_KPI_CONVERSION_RATE_NB_DAYS'); - // $data = array(); - // $from = strtotime(date('Y-m-d 00:00:00', strtotime('-31 day'))); - // $to = strtotime(date('Y-m-d 23:59:59', strtotime('-1 day'))); - // for ($date = $from; $date <= $to; $date = strtotime('+1 day', $date)) - // if (isset($visitors[$date]) && $visitors[$date]) - // $data[$date] = round(100 * ((isset($orders[$date]) && $orders[$date]) ? $orders[$date] : 0) / $visitors[$date], 2); - // else - // $data[$date] = 0; + $visitors = AdminStatsController::getVisits( + true, + date('Y-m-d', strtotime('-'.($nbDaysConversionRate + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + false /*'day'*/ + ); + + $orders = AdminStatsController::getOrders( + date('Y-m-d', strtotime('-'.($nbDaysConversionRate + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + false /*'day'*/ + ); $visits_sum = $visitors; //array_sum($visitors); $orders_sum = $orders; //array_sum($orders); @@ -641,7 +662,7 @@ class AdminStatsControllerCore extends AdminStatsTabController case 'disabled_room_types': if (AdminStatsController::getTotalRoomTypes()) { - $value = round(100 * AdminStatsController::getDisabledRoomTypes() / AdminStatsController::getTotalRoomTypes(), 2).'%'; + $value = round(100 * AdminStatsController::getDisabledRoomTypes(0) / AdminStatsController::getTotalRoomTypes(0), 2).'%'; } else { $value = '0%'; } @@ -681,10 +702,6 @@ class AdminStatsControllerCore extends AdminStatsTabController break; - case 'avg_customer_age': - $value = sprintf($this->l('%d years', null, null, false), AdminStatsController::getAverageCustomerAge(), 1); - break; - case 'pending_messages': $value = (int)AdminStatsController::getPendingMessages(); break; @@ -707,22 +724,6 @@ class AdminStatsControllerCore extends AdminStatsTabController $value = round(AdminStatsController::getMessagesPerThread(date('Y-m-d', strtotime('-31 day')), date('Y-m-d', strtotime('-1 day'))), 1); break; - case 'newsletter_registrations': - $value = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' - SELECT COUNT(*) - FROM `'._DB_PREFIX_.'customer` - WHERE newsletter = 1 - '.Shop::addSqlRestriction(Shop::SHARE_ORDER)); - if (Module::isInstalled('blocknewsletter')) { - $value += Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' - SELECT COUNT(*) - FROM `'._DB_PREFIX_.'newsletter` - WHERE active = 1 - '.Shop::addSqlRestriction(Shop::SHARE_ORDER)); - } - - break; - case 'enabled_languages': $value = Language::countActiveLanguages(); break; @@ -808,17 +809,105 @@ class AdminStatsControllerCore extends AdminStatsTabController } break; + + case 'best_selling_room_type': + $nbDaysBestSelling = Configuration::get('PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS'); + + if (!($idProduct = AdminStatsController::getBestSellingRoomType( + date('Y-m-d', strtotime('-'.($nbDaysBestSelling + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + 0 + ))) { + $value = $this->l('No room type', null, null, false); + } else { + $objProduct = new Product($idProduct, false, $this->context->language->id); + $value = $objProduct->name; + } + + break; + + case 'total_rooms': + $value = AdminStatsController::getTotalRooms(0); + + break; + + case 'total_online_bookable_rooms': + $value = AdminStatsController::getTotalRooms(0, 1); + + break; + + case 'total_offline_bookable_rooms': + $value = AdminStatsController::getTotalRooms(0, 0); + + break; + + case 'total_occupied_rooms': + $value = AdminStatsController::getTotalOccupiedRooms(0); + + break; + + case 'total_vacant_rooms': + $value = AdminStatsController::getAvailableRoomsForDiscreteDates(date('Y-m-d', strtotime('-1 day')), null, 0); + $value = $value[strtotime(date('Y-m-d', strtotime('-1 day')))]; + + break; + + case 'total_reserved_rooms': + $value = AdminStatsController::getTotalReservedRooms(0); + + break; + + case 'total_disabled_rooms': + $value = AdminStatsController::getDisabledRoomsForDiscreteDates(date('Y-m-d', strtotime('-1 day')), null, 0); + $value = $value[strtotime(date('Y-m-d', strtotime('-1 day')))]; + + break; + + case 'total_frequent_customers': + $nbOrdersFrequentCustomers = Configuration::get('PS_KPI_FREQUENT_CUSTOMER_NB_ORDERS'); + + $value = AdminStatsController::getTotalFrequentCustomers($nbOrdersFrequentCustomers, 0); + + break; + + case 'revenue_per_available_customer': + $nbDaysRevPac = Configuration::get('PS_KPI_REVPAC_NB_DAYS'); + + $value = AdminStatsController::getRevenuePerAvailableCustomer( + date('Y-m-d', strtotime('-'.($nbDaysRevPac + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + 0 + ); + + $value = Tools::displayPrice($value, $currency); + + break; + + case 'total_newsletter_registrations': + $value = AdminStatsController::getTotalNewsletterRegistrations(); + + break; + + case 'total_new_customers': + $nbDaysNewCustomers = Configuration::get('PS_KPI_NEW_CUSTOMERS_NB_DAYS'); + + $value = AdminStatsController::getTotalNewCustomers( + date('Y-m-d', strtotime('-'.($nbDaysNewCustomers + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + ); + + break; + + case 'total_banned_customers': + $value = AdminStatsController::getTotalBannedCustomers(); + + break; + default: $value = false; } - if ($value !== false) { - $array = array('value' => $value); - if (isset($data)) { - $array['data'] = $data; - } - die(json_encode($array)); - } - die(json_encode(array('has_errors' => true))); + + return $value; } public static function getFrontOfficeTranslations() @@ -946,12 +1035,17 @@ class AdminStatsControllerCore extends AdminStatsTabController ); } - public static function getTotalRooms($idHotel = false) + public static function getTotalRooms($idHotel = null, $showAtFront = null) { - return Db::getInstance()->getValue( - 'SELECT COUNT(`id`) FROM `'._DB_PREFIX_.'htl_room_information` hri - WHERE 1'.HotelBranchInformation::addHotelRestriction($idHotel, 'hri') - ); + $sql = 'SELECT COUNT(hri.`id`) + FROM `'._DB_PREFIX_.'htl_room_information` hri + INNER JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE p.`booking_product` = 1 '. + (!is_null($showAtFront) ? ' AND p.`show_at_front` = '.(int) $showAtFront : ''). + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : ''); + + return Db::getInstance()->getValue($sql); } public static function getOccupancyData($dateFrom, $dateTo, $idsHotel = false) @@ -1557,6 +1651,174 @@ class AdminStatsControllerCore extends AdminStatsTabController return $result; } + public static function getDisabledRoomsForDiscreteDates($dateFrom, $dateTo = null, $idHotel = null, $useCache = true) + { + $dateTo = !$dateTo ? date('Y-m-d', strtotime('+1 day', strtotime($dateFrom))) : $dateTo; + $discreteDates = array(); + $dateTemp = $dateFrom; + while ($dateTemp <= $dateTo) { + $dateNext = date('Y-m-d', strtotime('+1 day', strtotime($dateTemp))); + $discreteDates[] = array( + 'date_from' => $dateTemp, + 'date_to' => $dateNext, + 'timestamp_from' => strtotime($dateTemp), + ); + $dateTemp = $dateNext; + }; + + $result = array(); + foreach ($discreteDates as $discreteDate) { + $cacheKey = 'AdminStats::getDisabledRoomsForDiscreteDates'.'_'.(int) $discreteDate['timestamp_from'].'_'. + (!is_array($idHotel) ? (int) $idHotel : implode('_', $idHotel)); + if (!Cache::isStored($cacheKey) || !$useCache) { + $sql = 'SELECT (num_inactive + num_temporarily_inactive) AS num_disabled + FROM ( + SELECT ( + SELECT IFNULL(COUNT(hri.`id`), 0) + FROM `'._DB_PREFIX_.'htl_room_information` hri + LEFT JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE hri.`id_status` = '.(int) HotelRoomInformation::STATUS_INACTIVE.' + AND p.`active` = 1'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : '').' + ) AS num_inactive, + ( + SELECT IFNULL(COUNT(hri.`id`), 0) + FROM `'._DB_PREFIX_.'htl_room_information` hri + LEFT JOIN `'._DB_PREFIX_.'htl_room_disable_dates` hrdd + ON (hrdd.`id_room` = hri.`id`) + LEFT JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE hri.`id_status` = '.(int) HotelRoomInformation::STATUS_TEMPORARY_INACTIVE.' + AND ("'.pSQL($discreteDate['date_from']).'" >= hrdd.`date_from` AND "'.pSQL($discreteDate['date_from']).'" < hrdd.`date_to`) + AND p.`active` = 1'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : '').' + ) AS num_temporarily_inactive + ) AS t'; + + $value = Db::getInstance()->getValue($sql); + Cache::store($cacheKey, $value); + } + + $result[$discreteDate['timestamp_from']] = Cache::retrieve($cacheKey); + } + + return $result; + } + + public static function getTotalOccupiedRooms($idHotel = null) + { + $sql = 'SELECT COUNT(DISTINCT hbd.`id_room`) + FROM `'._DB_PREFIX_.'htl_booking_detail` hbd + LEFT JOIN `'._DB_PREFIX_.'htl_room_information` hri + ON (hri.`id` = hbd.`id_room`) + LEFT JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE p.`active` = 1 + AND hbd.`is_refunded` = 0 + AND hbd.`is_cancelled` = 0 + AND hbd.`id_status` = '.(int) HotelBookingDetail::STATUS_CHECKED_IN. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hbd') : ''); + $result = Db::getInstance()->getValue($sql); + + return $result; + } + + public static function getTotalReservedRooms($idHotel = null) + { + $dateToday = date('Y-m-d'); + + $sql = 'SELECT COUNT(DISTINCT hbd.`id_room`) + FROM `'._DB_PREFIX_.'htl_booking_detail` hbd + LEFT JOIN `'._DB_PREFIX_.'htl_room_information` hri + ON (hri.`id` = hbd.`id_room`) + LEFT JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE p.`active` = 1 + AND hbd.`is_refunded` = 0 + AND hbd.`is_cancelled` = 0 + AND hbd.`id_status` != '.(int) HotelBookingDetail::STATUS_CHECKED_IN.' + AND hbd.`date_from` = "'.pSQL($dateToday).' 00:00:00"'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hbd') : ''); + $result = Db::getInstance()->getValue($sql); + + return $result; + } + + public static function getTotalFrequentCustomers($nbOrders = 5, $idHotel = null) + { + $sql = 'SELECT COUNT(t.`id_customer`) + FROM ( + SELECT o.`id_customer`, COUNT(o.`id_order`) AS nb_orders, + ( + SELECT hbd.`id_hotel` + FROM `'._DB_PREFIX_.'htl_booking_detail` hbd + WHERE hbd.`id_order` = o.`id_order` LIMIT 1 + ) AS id_hotel + FROM `'._DB_PREFIX_.'orders` o + WHERE o.`valid` = 1 + GROUP BY o.`id_customer` + HAVING 1 '.(!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel) : '').' + ) AS t + WHERE t.`nb_orders` >= '.(int) $nbOrders; + $result = Db::getInstance()->getValue($sql); + + return $result; + } + + public static function getRevenuePerAvailableCustomer($dateFrom, $dateTo, $idHotel = null) + { + $totalRevenue = self::getRevenue($dateFrom, $dateTo, $idHotel); + + $totalCustomers = Db::getInstance()->getValue( + 'SELECT COUNT(c.`id_customer`) + FROM `'._DB_PREFIX_.'customer` c + WHERE c.`date_add` <= "'.pSQL($dateTo).' 23:59:59"' + ); + + return $totalCustomers ? $totalRevenue / $totalCustomers : 0; + } + + public static function getTotalNewsletterRegistrations() + { + $customerRegistrations = Db::getInstance()->getValue( + 'SELECT COUNT(c.`id_customer`) + FROM `'._DB_PREFIX_.'customer` c + WHERE c.`newsletter` = 1' + ); + + $visitorRegistrations = 0; + if (Module::isInstalled('blocknewsletter')) { + $visitorRegistrations = Db::getInstance()->getValue( + 'SELECT COUNT(n.`id`) + FROM `'._DB_PREFIX_.'newsletter` n + WHERE n.`active` = 1' + ); + } + + return $customerRegistrations + $visitorRegistrations; + } + + public static function getTotalNewCustomers($dateFrom, $dateTo) + { + $sql = 'SELECT COUNT(c.`id_customer`) + FROM `'._DB_PREFIX_.'customer` c + WHERE c.`date_add` >= "'.pSQL($dateFrom).' 00:00:00" AND c.`date_add` <= "'.pSQL($dateTo).' 23:59:59"'; + $result = Db::getInstance()->getValue($sql); + + return $result; + } + + public static function getTotalBannedCustomers() + { + $sql = 'SELECT COUNT(c.`id_customer`) + FROM `'._DB_PREFIX_.'customer` c + WHERE c.`deleted` = 1'; + $result = Db::getInstance()->getValue($sql); + + return $result; + } + public static function getOccupancyRateForDiscreteDates($dateFrom, $dateTo, $idHotel = null) { if ($dateFrom == $dateTo) { diff --git a/controllers/admin/AdminTranslationsController.php b/controllers/admin/AdminTranslationsController.php index 71ae8481b..5206b05e0 100644 --- a/controllers/admin/AdminTranslationsController.php +++ b/controllers/admin/AdminTranslationsController.php @@ -1325,7 +1325,7 @@ class AdminTranslationsControllerCore extends AdminController $helper->href = $this->context->link->getAdminLink('AdminLanguages'); $helper->title = $this->l('Enabled Languages', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=enabled_languages'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-fo-translations'; @@ -1333,7 +1333,7 @@ class AdminTranslationsControllerCore extends AdminController $helper->color = 'color3'; $helper->title = $this->l('Front office Translations', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=frontoffice_translations'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-bo-translations'; @@ -1345,7 +1345,7 @@ class AdminTranslationsControllerCore extends AdminController } $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=backoffice_translations'; $helper->refresh = (bool)(ConfigurationKPI::get('BACKOFFICE_TRANSLATIONS_EXPIRE') < $time); - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/install/data/xml/configuration.xml b/install/data/xml/configuration.xml index 935de20ba..023e59636 100644 --- a/install/data/xml/configuration.xml +++ b/install/data/xml/configuration.xml @@ -101,6 +101,9 @@ 20 + + 30 + 0 @@ -884,6 +887,18 @@ 1 + + 5 + + + 30 + + + 30 + + + 30 + 0 diff --git a/js/admin.js b/js/admin.js index 4d474721a..49d636379 100644 --- a/js/admin.js +++ b/js/admin.js @@ -1551,6 +1551,54 @@ function refresh_kpis() }); } +$(document).on('change', '.kpi-container .kpi-display-toggle', function (e) { + let kpiCheckbox = $(this); + let isVisible = parseInt(kpiCheckbox.is(':checked') & 1); + + // hide or show the KPI + $('#' + kpiCheckbox.attr('data-kpi-id')).parent().toggle(isVisible); + + // save visibility state to cookie + $.ajax({ + type: 'POST', + headers: { 'cache-control': 'no-cache' }, + url: window.location, + cache: false, + dataType: 'json', + data: { + ajax: 1, + controller: help_class_name, + action: 'changeKpiVisibility', + kpi_id: kpiCheckbox.attr('data-kpi-id'), + is_visible: isVisible, + } + }); +}); + +// prevent dropdown from closing on clicking its body +$(document).on('click', '.kpi-container .dropdown-menu', function (e) { + e.stopPropagation(); +}); + +function toggleKpiView() { + $('.kpi-container').toggleClass('no-wrapping'); // default view is to wrap KPIs + + // save view to cookie + $.ajax({ + type: 'POST', + headers: { 'cache-control': 'no-cache' }, + url: window.location, + cache: false, + dataType: 'json', + data: { + ajax: 1, + controller: help_class_name, + action: 'saveKpiView', + no_wrapping: parseInt($('.kpi-container').hasClass('no-wrapping') & 1), + } + }); +} + function createSqlQueryName() { var container = false;