class-admin-view.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. <?php
  2. /**
  3. * Admin View Class - Handles admin interface rendering
  4. *
  5. * @package StudiouWCCustomReports
  6. */
  7. if (!defined('ABSPATH')) {
  8. exit;
  9. }
  10. class StudiouWC_Admin_View {
  11. private $db_manager;
  12. private $settings_manager;
  13. public function __construct($db_manager, $settings_manager) {
  14. $this->db_manager = $db_manager;
  15. $this->settings_manager = $settings_manager;
  16. }
  17. public function add_admin_menu() {
  18. // Add Custom Reports page under WooCommerce > Reports
  19. add_submenu_page(
  20. 'woocommerce',
  21. __('Custom Reports', StudiouWC_Main::get_text_domain()),
  22. __('Custom Reports', StudiouWC_Main::get_text_domain()),
  23. 'manage_woocommerce',
  24. 'studiou-custom-reports',
  25. array($this, 'display_custom_reports_page')
  26. );
  27. // Add Settings page under WooCommerce > Reports Settings
  28. add_submenu_page(
  29. 'woocommerce',
  30. __('Custom Report Settings', StudiouWC_Main::get_text_domain()),
  31. __('Custom Report Settings', StudiouWC_Main::get_text_domain()),
  32. 'manage_woocommerce',
  33. 'studiou-reports-settings',
  34. array($this, 'display_settings_page')
  35. );
  36. }
  37. public function enqueue_admin_scripts($hook) {
  38. if (strpos($hook, 'studiou-') !== false) {
  39. wp_enqueue_script('jquery');
  40. wp_enqueue_script('jquery-ui-datepicker');
  41. wp_enqueue_style('jquery-ui-datepicker', 'https://code.jquery.com/ui/1.12.1/themes/ui-lightness/jquery-ui.css');
  42. }
  43. }
  44. public function display_custom_reports_page() {
  45. $settings = $this->settings_manager->get_settings();
  46. // Handle form submission
  47. $view = isset($_GET['view']) ? sanitize_text_field($_GET['view']) : 'product_categories_summary';
  48. $date_from = isset($_GET['date_from']) ? sanitize_text_field($_GET['date_from']) : $this->settings_manager->get_default_date_from();
  49. $date_to = isset($_GET['date_to']) ? sanitize_text_field($_GET['date_to']) : date('Y-m-d');
  50. // If no statuses selected, use all available statuses (including custom ones)
  51. if (isset($_GET['statuses']) && !empty($_GET['statuses'])) {
  52. $statuses = array_map('sanitize_text_field', $_GET['statuses']);
  53. } else {
  54. $default_statuses = $settings['default_statuses'] ?? array();
  55. if (empty($default_statuses)) {
  56. // Include common statuses + your custom status
  57. $statuses = array('wc-completed', 'wc-processing', 'wc-on-hold', 'wc-to-print');
  58. } else {
  59. $statuses = $default_statuses;
  60. }
  61. }
  62. $per_page = isset($_GET['per_page']) ? intval($_GET['per_page']) : 20;
  63. $paged = isset($_GET['paged']) ? intval($_GET['paged']) : 1;
  64. $default_orderby = ($view === 'order_customers_list') ? 'customer_name' : 'category';
  65. $orderby = isset($_GET['orderby']) ? sanitize_text_field($_GET['orderby']) : $default_orderby;
  66. $order = isset($_GET['order']) ? sanitize_text_field($_GET['order']) : 'ASC';
  67. // Get report data based on view
  68. if ($view === 'order_customers_list') {
  69. $report_data = $this->db_manager->get_order_customers_list($date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order);
  70. $total_items = $this->db_manager->get_order_customers_total_items($date_from, $date_to, $statuses);
  71. } else {
  72. $report_data = $this->db_manager->get_product_categories_summary($date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order);
  73. $total_items = $this->db_manager->get_total_items($date_from, $date_to, $statuses);
  74. }
  75. $total_pages = ceil($total_items / $per_page);
  76. $this->render_reports_page($view, $date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order, $report_data, $total_items, $total_pages, $settings);
  77. }
  78. public function display_settings_page() {
  79. $settings = $this->settings_manager->get_settings();
  80. $this->render_settings_page($settings);
  81. }
  82. private function render_reports_page($view, $date_from, $date_to, $statuses, $per_page, $paged, $orderby, $order, $report_data, $total_items, $total_pages, $settings) {
  83. ?>
  84. <div class="wrap">
  85. <h1><?php _e('Custom Reports', StudiouWC_Main::get_text_domain()); ?></h1>
  86. <?php $this->render_filter_form($view, $date_from, $date_to, $statuses, $per_page); ?>
  87. <?php if (!empty($report_data)): ?>
  88. <?php $this->render_pagination_top($total_items, $total_pages, $paged); ?>
  89. <?php if ($view === 'order_customers_list'): ?>
  90. <?php $this->render_customers_data_table($report_data, $orderby, $order); ?>
  91. <?php else: ?>
  92. <?php $this->render_data_table($report_data, $orderby, $order, $settings); ?>
  93. <?php endif; ?>
  94. <?php else: ?>
  95. <p><?php _e('No data found for the selected criteria.', StudiouWC_Main::get_text_domain()); ?></p>
  96. <?php endif; ?>
  97. </div>
  98. <?php $this->render_reports_javascript(); ?>
  99. <?php
  100. }
  101. private function render_filter_form($view, $date_from, $date_to, $statuses, $per_page) {
  102. ?>
  103. <form method="get" id="studiou-reports-form">
  104. <input type="hidden" name="page" value="studiou-custom-reports">
  105. <table class="form-table">
  106. <tr>
  107. <th><label for="view"><?php _e('View', StudiouWC_Main::get_text_domain()); ?></label></th>
  108. <td>
  109. <select name="view" id="view">
  110. <option value="product_categories_summary" <?php selected($view, 'product_categories_summary'); ?>>
  111. <?php _e('Product Categories and Variants Summary', StudiouWC_Main::get_text_domain()); ?>
  112. </option>
  113. <option value="order_customers_list" <?php selected($view, 'order_customers_list'); ?>>
  114. <?php _e('Order Customers List', StudiouWC_Main::get_text_domain()); ?>
  115. </option>
  116. </select>
  117. </td>
  118. </tr>
  119. <tr>
  120. <th><label for="date_from"><?php _e('Date From', StudiouWC_Main::get_text_domain()); ?></label></th>
  121. <td><input type="date" name="date_from" id="date_from" value="<?php echo esc_attr($date_from); ?>"></td>
  122. </tr>
  123. <tr>
  124. <th><label for="date_to"><?php _e('Date To', StudiouWC_Main::get_text_domain()); ?></label></th>
  125. <td><input type="date" name="date_to" id="date_to" value="<?php echo esc_attr($date_to); ?>"></td>
  126. </tr>
  127. <tr>
  128. <th><label for="statuses"><?php _e('Included Statuses', StudiouWC_Main::get_text_domain()); ?></label></th>
  129. <td>
  130. <?php $this->render_status_checkboxes($statuses); ?>
  131. </td>
  132. </tr>
  133. <tr>
  134. <th><label for="per_page"><?php _e('Items per page', StudiouWC_Main::get_text_domain()); ?></label></th>
  135. <td>
  136. <select name="per_page" id="per_page">
  137. <option value="10" <?php selected($per_page, 10); ?>>10</option>
  138. <option value="20" <?php selected($per_page, 20); ?>>20</option>
  139. <option value="50" <?php selected($per_page, 50); ?>>50</option>
  140. <option value="100" <?php selected($per_page, 100); ?>>100</option>
  141. </select>
  142. </td>
  143. </tr>
  144. </table>
  145. <p class="submit">
  146. <input type="submit" name="filter" class="button-primary" value="<?php esc_attr_e('Filter', StudiouWC_Main::get_text_domain()); ?>">
  147. <button type="button" id="export-csv" class="button"><?php _e('Export', StudiouWC_Main::get_text_domain()); ?></button>
  148. </p>
  149. </form>
  150. <?php
  151. }
  152. private function render_status_checkboxes($selected_statuses) {
  153. $order_statuses = wc_get_order_statuses();
  154. // Add custom statuses that might not be in the standard list
  155. global $wpdb;
  156. $custom_statuses = $wpdb->get_col("
  157. SELECT DISTINCT status
  158. FROM {$wpdb->prefix}wc_orders
  159. WHERE status NOT IN ('" . implode("','", array_keys($order_statuses)) . "')
  160. AND date_created_gmt >= DATE_SUB(NOW(), INTERVAL 90 DAY)
  161. ");
  162. foreach ($custom_statuses as $custom_status) {
  163. if (!isset($order_statuses[$custom_status])) {
  164. $order_statuses[$custom_status] = ucwords(str_replace(array('wc-', '-'), array('', ' '), $custom_status));
  165. }
  166. }
  167. foreach ($order_statuses as $status_key => $status_label) {
  168. $checked = in_array($status_key, $selected_statuses) ? 'checked' : '';
  169. echo '<label><input type="checkbox" name="statuses[]" value="' . esc_attr($status_key) . '" ' . $checked . '> ' . esc_html($status_label) . '</label><br>';
  170. }
  171. }
  172. private function render_pagination_top($total_items, $total_pages, $paged) {
  173. ?>
  174. <div class="tablenav top">
  175. <div class="tablenav-pages">
  176. <span class="displaying-num"><?php
  177. /* translators: %d: number of items */
  178. printf(_n('%d item', '%d items', $total_items, StudiouWC_Main::get_text_domain()), $total_items);
  179. ?></span>
  180. <?php if ($total_pages > 1): ?>
  181. <span class="pagination-links">
  182. <?php if ($paged > 1): ?>
  183. <a class="prev-page button" href="<?php echo esc_url(add_query_arg('paged', $paged - 1)); ?>" title="<?php esc_attr_e('Previous', StudiouWC_Main::get_text_domain()); ?>"><?php _e('‹', StudiouWC_Main::get_text_domain()); ?></a>
  184. <?php endif; ?>
  185. <span class="paging-input">
  186. <span class="tablenav-paging-text"><?php
  187. /* translators: 1: current page number, 2: total number of pages */
  188. printf(__('%1$d of %2$d', StudiouWC_Main::get_text_domain()), $paged, $total_pages);
  189. ?></span>
  190. </span>
  191. <?php if ($paged < $total_pages): ?>
  192. <a class="next-page button" href="<?php echo esc_url(add_query_arg('paged', $paged + 1)); ?>" title="<?php esc_attr_e('Next', StudiouWC_Main::get_text_domain()); ?>"><?php _e('›', StudiouWC_Main::get_text_domain()); ?></a>
  193. <?php endif; ?>
  194. </span>
  195. <?php endif; ?>
  196. </div>
  197. </div>
  198. <?php
  199. }
  200. private function render_data_table($report_data, $orderby, $order, $settings) {
  201. $default_property = $settings['default_property'] ?? '';
  202. $property_values = array();
  203. if (!empty($default_property)) {
  204. $property_values = $this->db_manager->get_property_values($default_property);
  205. }
  206. ?>
  207. <table class="wp-list-table widefat fixed striped">
  208. <thead>
  209. <tr>
  210. <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'category', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Product Category', StudiouWC_Main::get_text_domain()); ?></a></th>
  211. <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'orders_count', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Orders Count', StudiouWC_Main::get_text_domain()); ?></a></th>
  212. <?php foreach ($property_values as $slug => $name): ?>
  213. <th><?php
  214. /* translators: %s: property name */
  215. printf(__('%s Count', StudiouWC_Main::get_text_domain()), esc_html($name));
  216. ?></th>
  217. <?php endforeach; ?>
  218. <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'total_price', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Total Price', StudiouWC_Main::get_text_domain()); ?></a></th>
  219. </tr>
  220. </thead>
  221. <tbody>
  222. <?php foreach ($report_data as $row): ?>
  223. <tr>
  224. <td><?php echo esc_html($row['category']); ?></td>
  225. <td><?php echo esc_html($row['orders_count']); ?></td>
  226. <?php foreach ($property_values as $slug => $name): ?>
  227. <td><?php echo esc_html($row['property_counts'][$slug] ?? 0); ?></td>
  228. <?php endforeach; ?>
  229. <td><?php echo wc_price($row['total_price']); ?></td>
  230. </tr>
  231. <?php endforeach; ?>
  232. </tbody>
  233. </table>
  234. <?php
  235. }
  236. private function render_customers_data_table($report_data, $orderby, $order) {
  237. ?>
  238. <table class="wp-list-table widefat fixed striped">
  239. <thead>
  240. <tr>
  241. <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'customer_name', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Customer Name', StudiouWC_Main::get_text_domain()); ?></a></th>
  242. <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'customer_email', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Email', StudiouWC_Main::get_text_domain()); ?></a></th>
  243. <th><?php _e('Phone', StudiouWC_Main::get_text_domain()); ?></th>
  244. <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'orders_count', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Orders Count', StudiouWC_Main::get_text_domain()); ?></a></th>
  245. <th><a href="<?php echo esc_url(add_query_arg(array('orderby' => 'total_price', 'order' => $order === 'ASC' ? 'DESC' : 'ASC'))); ?>"><?php _e('Total Orders Price', StudiouWC_Main::get_text_domain()); ?></a></th>
  246. </tr>
  247. </thead>
  248. <tbody>
  249. <?php foreach ($report_data as $row): ?>
  250. <tr>
  251. <td><?php echo esc_html($row['customer_name']); ?></td>
  252. <td><?php echo esc_html($row['customer_email']); ?></td>
  253. <td><?php echo esc_html($row['customer_phone']); ?></td>
  254. <td><?php echo esc_html($row['orders_count']); ?></td>
  255. <td><?php echo wc_price($row['total_price']); ?></td>
  256. </tr>
  257. <?php endforeach; ?>
  258. </tbody>
  259. </table>
  260. <?php
  261. }
  262. private function render_reports_javascript() {
  263. ?>
  264. <script>
  265. jQuery(document).ready(function($) {
  266. $('#export-csv').click(function() {
  267. var formData = $('#studiou-reports-form').serialize();
  268. formData += '&action=studiou_export_csv&export=1';
  269. var form = $('<form method="post" action="' + ajaxurl + '"></form>');
  270. $.each(formData.split('&'), function(i, field) {
  271. var parts = field.split('=');
  272. if (parts.length === 2) {
  273. form.append('<input type="hidden" name="' + decodeURIComponent(parts[0]) + '" value="' + decodeURIComponent(parts[1]) + '">');
  274. }
  275. });
  276. $('body').append(form);
  277. form.submit();
  278. form.remove();
  279. });
  280. });
  281. </script>
  282. <?php
  283. }
  284. private function render_settings_page($settings) {
  285. ?>
  286. <div class="wrap">
  287. <h1><?php _e('Custom Report Settings', StudiouWC_Main::get_text_domain()); ?></h1>
  288. <form method="post" id="studiou-settings-form">
  289. <?php wp_nonce_field('studiou_save_settings', 'studiou_settings_nonce'); ?>
  290. <table class="form-table">
  291. <tr>
  292. <th><label for="default_interval"><?php _e('Default shown interval', StudiouWC_Main::get_text_domain()); ?></label></th>
  293. <td>
  294. <select name="default_interval" id="default_interval">
  295. <option value="last_day" <?php selected($settings['default_interval'] ?? '', 'last_day'); ?>><?php _e('Last day', StudiouWC_Main::get_text_domain()); ?></option>
  296. <option value="last_week" <?php selected($settings['default_interval'] ?? '', 'last_week'); ?>><?php _e('Last week', StudiouWC_Main::get_text_domain()); ?></option>
  297. <option value="last_month" <?php selected($settings['default_interval'] ?? '', 'last_month'); ?>><?php _e('Last month', StudiouWC_Main::get_text_domain()); ?></option>
  298. </select>
  299. </td>
  300. </tr>
  301. <tr>
  302. <th><label for="default_property"><?php _e('Default property', StudiouWC_Main::get_text_domain()); ?></label></th>
  303. <td>
  304. <select name="default_property" id="default_property">
  305. <option value=""><?php _e('Select property', StudiouWC_Main::get_text_domain()); ?></option>
  306. <?php
  307. $properties = $this->db_manager->get_product_properties();
  308. foreach ($properties as $slug => $name) {
  309. $selected = selected($settings['default_property'] ?? '', $slug, false);
  310. echo '<option value="' . esc_attr($slug) . '" ' . $selected . '>' . esc_html($name) . '</option>';
  311. }
  312. ?>
  313. </select>
  314. </td>
  315. </tr>
  316. <tr>
  317. <th><label for="default_statuses"><?php _e('Default Included statuses', StudiouWC_Main::get_text_domain()); ?></label></th>
  318. <td>
  319. <?php
  320. $order_statuses = wc_get_order_statuses();
  321. $default_statuses = $settings['default_statuses'] ?? array();
  322. foreach ($order_statuses as $status_key => $status_label) {
  323. $checked = in_array($status_key, $default_statuses) ? 'checked' : '';
  324. echo '<label><input type="checkbox" name="default_statuses[]" value="' . esc_attr($status_key) . '" ' . $checked . '> ' . esc_html($status_label) . '</label><br>';
  325. }
  326. ?>
  327. </td>
  328. </tr>
  329. </table>
  330. <p class="submit">
  331. <button type="button" id="save-settings" class="button-primary"><?php _e('Save Settings', StudiouWC_Main::get_text_domain()); ?></button>
  332. </p>
  333. </form>
  334. </div>
  335. <script>
  336. jQuery(document).ready(function($) {
  337. $('#save-settings').click(function() {
  338. var formData = $('#studiou-settings-form').serialize();
  339. formData += '&action=studiou_save_settings';
  340. $.post(ajaxurl, formData, function(response) {
  341. if (response.success) {
  342. alert('<?php echo esc_js(__('Settings saved successfully!', StudiouWC_Main::get_text_domain())); ?>');
  343. } else {
  344. alert('<?php echo esc_js(__('Error saving settings.', StudiouWC_Main::get_text_domain())); ?>');
  345. }
  346. }).fail(function() {
  347. alert('<?php echo esc_js(__('Connection error. Please try again.', StudiouWC_Main::get_text_domain())); ?>');
  348. });
  349. });
  350. });
  351. </script>
  352. <?php
  353. }
  354. }