label, value). * @type array $summary_cards List of cards: title, value, value_type, icon, change, is_positive, * previous, note, no_previous_label. * @type array $charts List of charts: id, title, date_label, full_width, type, * data, x_key, series ( key, label, color, style, aggregate: * 'average' for a series that must not add up by week or * month ), label_key, value_key, value_format, empty. * @type array $widgets List of bottom widgets: title, render (callable). * } */ public function __construct( array $config ) { $this->config = $config; $this->columns = ! empty( $config['columns'] ) ? absint( $config['columns'] ) : 5; $header = isset( $config['header'] ) && is_array( $config['header'] ) ? $config['header'] : []; // Date range: use the override when supplied, otherwise resolve from the request. $this->date_range = ! empty( $header['date_range'] ) && is_array( $header['date_range'] ) ? $header['date_range'] : DDFW_Dashboard_Data::get_date_range(); $this->render(); } /** * Render the full dashboard. * * @return void */ protected function render() { $this->enqueue_assets(); ?>
render_header(); $this->render_summary_cards(); $this->render_charts(); $this->render_widgets(); ?>
localize(); } /** * Enqueue the shared dashboard assets registered by DDFW_Assets. * * @return void */ protected function enqueue_assets() { wp_enqueue_style( 'ddfw-dashboard-analytics-style' ); wp_enqueue_script( 'ddfw-dashboard-analytics-script' ); } /** * Render the dashboard header ( avatar + welcome + date filter ). * * @return void */ protected function render_header() { $header = isset( $this->config['header'] ) && is_array( $this->config['header'] ) ? $this->config['header'] : []; $show_avatar = ! isset( $header['show_avatar'] ) || $header['show_avatar']; $show_date_filter = ! isset( $header['show_date_filter'] ) || $header['show_date_filter']; $current_user = wp_get_current_user(); $welcome = isset( $header['welcome'] ) ? $header['welcome'] : esc_html__( 'Welcome back!', 'affiliates-for-woocommerce' ); if ( false !== strpos( $welcome, '%s' ) ) { $welcome = sprintf( $welcome, $current_user->display_name ); } $subtitle = isset( $header['subtitle'] ) ? $header['subtitle'] : ''; // Detail screens ( e.g. a single user ) can point the header at a specific entity instead // of the current admin: override the avatar and add a back link. $avatar_url = ! empty( $header['avatar_url'] ) ? $header['avatar_url'] : get_avatar_url( $current_user->ID, [ 'size' => 48 ] ); $back_link = isset( $header['back_link'] ) && is_array( $header['back_link'] ) ? $header['back_link'] : []; ?>
render_date_filter(); } $this->render_header_actions( isset( $header['actions'] ) && is_array( $header['actions'] ) ? $header['actions'] : [] ); ?>
config['header'] ) && is_array( $this->config['header'] ) ? $this->config['header'] : []; $extra = isset( $header['date_filter_extra'] ) && is_array( $header['date_filter_extra'] ) ? $header['date_filter_extra'] : []; // Extra selects (an outlet, a store, a vendor) live in the same form as the dates, so // changing one keeps the other. A filter's own name is never also sent as a hidden field. $filters = isset( $header['filters'] ) && is_array( $header['filters'] ) ? $header['filters'] : []; $extra = array_diff_key( $extra, array_flip( wp_list_pluck( $filters, 'name' ) ) ); $presets = DDFW_Dashboard_Data::get_presets(); $groups = DDFW_Dashboard_Data::get_preset_groups(); $active = $this->date_range['key'] ?? '30_days'; $check = ''; ?>
$extra_value ) : ?>
> config['summary_cards'] ) && is_array( $this->config['summary_cards'] ) ? $this->config['summary_cards'] : []; if ( empty( $cards ) ) { return; } // Optional panel rendered beside the cards ( e.g. a user level / rank widget ) that // spans the full height of the card grid instead of dropping to the bottom section. $aside = isset( $this->config['summary_aside'] ) && is_array( $this->config['summary_aside'] ) ? $this->config['summary_aside'] : []; ?>
render_summary_card( $card ); } ?>

= 1000 ? '999+' : number_format_i18n( abs( $change ), fmod( $change, 1 ) ? 1 : 0 ) ) . '%' ); ?>

config['charts'] ) && is_array( $this->config['charts'] ) ? $this->config['charts'] : []; if ( empty( $charts ) ) { return; } // Group consecutive non-full-width charts into rows of two; full-width gets its own row. $full = []; $half = []; foreach ( $charts as $chart ) { if ( ! empty( $chart['full_width'] ) ) { $full[] = $chart; } else { $half[] = $chart; } } // Full-width charts each on their own row. foreach ( $full as $chart ) { ?>
render_chart_container( $chart ); ?>
render_chart_container( $chart ); } ?>
collect_chart( $chart ); $title = $chart['title'] ?? ''; $date_label = $chart['date_label'] ?? ''; ?>

render_chart_canvas( $canvas_id ); ?>
charts_js[] = [ 'canvasId' => $canvas_id, 'type' => $chart['type'] ?? 'line', 'data' => array_values( $chart['data'] ?? [] ), 'xKey' => $chart['x_key'] ?? 'date', 'series' => array_values( $chart['series'] ?? [] ), 'labelKey' => $chart['label_key'] ?? 'label', 'valueKey' => $chart['value_key'] ?? 'value', 'valueFormat' => $chart['value_format'] ?? 'number', 'empty' => [ 'title' => $chart['empty']['title'] ?? esc_html__( 'No data available', 'affiliates-for-woocommerce' ), 'desc' => $chart['empty']['desc'] ?? esc_html__( 'Data will appear here once activity is recorded.', 'affiliates-for-woocommerce' ), ], ]; return $canvas_id; } /** * Output a chart canvas wrapper for a previously collected chart. * * @param string $canvas_id Canvas element id. * @return void */ protected function render_chart_canvas( $canvas_id ) { ?>
config['widgets'] ) && is_array( $this->config['widgets'] ) ? $this->config['widgets'] : []; if ( empty( $widgets ) ) { return; } ?>

render_chart_canvas( $this->collect_chart( $widget['chart'] ) ); } elseif ( ! empty( $widget['render'] ) && is_callable( $widget['render'] ) ) { call_user_func( $widget['render'] ); } ?>
tag ( `$product->get_image()` ) or an image URL. * @type string $shape 'square' ( products, the default ) or 'round' ( people ). * @type string $value Optional value on the right ( HTML: `wc_price()`, a status badge ). * @type string $value_class Optional 'positive' or 'negative' for a tinted signed value. * } * @param string $empty Message shown when there are no rows. * @param bool $image Whether rows carry an image column. Rows without one get the placeholder tile. * @return void */ public static function render_list( $items, $empty = '', $image = true ) { if ( empty( $items ) ) { ?>
< class="ddfw-dash-list-item"> ' : wp_kses_post( $source ); ?> >
html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' ), 'position' => get_option( 'woocommerce_currency_pos', 'left' ), 'decimals' => wc_get_price_decimals(), 'decimalSeparator' => wc_get_price_decimal_separator(), 'thousandSeparator' => wc_get_price_thousand_separator(), ] : [ 'symbol' => '$' ]; $currency = apply_filters( 'ddfw_dashboard_currency', $currency ); wp_localize_script( 'ddfw-dashboard-analytics-script', 'ddfwDashboardAnalytics', [ 'charts' => $this->charts_js, 'dateRange' => $this->date_range, 'currency' => $currency, 'currencySymbol' => $currency['symbol'], 'today' => current_time( 'Y-m-d' ), 'startOfWeek' => (int) get_option( 'start_of_week', 1 ), 'i18n' => [ 'noData' => esc_html__( 'No data available', 'affiliates-for-woocommerce' ), 'total' => esc_html__( 'Total', 'affiliates-for-woocommerce' ), 'pickStart' => esc_html__( 'Pick a start date', 'affiliates-for-woocommerce' ), 'pickEnd' => esc_html__( 'Now pick the end date', 'affiliates-for-woocommerce' ), 'previousMonth' => esc_html__( 'Previous month', 'affiliates-for-woocommerce' ), 'nextMonth' => esc_html__( 'Next month', 'affiliates-for-woocommerce' ), /* translators: %s: the period still being counted, e.g. "Oct 19 – 25, 2026". */ 'soFar' => esc_html__( '%s (so far)', 'affiliates-for-woocommerce' ), 'busiestDay' => esc_html__( 'Busiest day', 'affiliates-for-woocommerce' ), 'busiestHour' => esc_html__( 'Busiest hour', 'affiliates-for-woocommerce' ), 'less' => esc_html__( 'Less', 'affiliates-for-woocommerce' ), 'more' => esc_html__( 'More', 'affiliates-for-woocommerce' ), 'day' => esc_html__( 'Day', 'affiliates-for-woocommerce' ), 'week' => esc_html__( 'Week', 'affiliates-for-woocommerce' ), 'month' => esc_html__( 'Month', 'affiliates-for-woocommerce' ), 'line' => esc_html__( 'Line', 'affiliates-for-woocommerce' ), 'bar' => esc_html__( 'Bar', 'affiliates-for-woocommerce' ), ], ] ); } } }