customer reports
This commit is contained in:
parent
87e3d83b67
commit
abe07eb787
|
@ -44,7 +44,6 @@ function woocommerce_admin_scripts() {
|
|||
wp_enqueue_script('jquery-ui-datepicker', $woocommerce->plugin_url() . '/assets/js/admin/ui-datepicker.js', array('jquery','jquery-ui-core') );
|
||||
wp_enqueue_script( 'flot', $woocommerce->plugin_url() . '/assets/js/admin/jquery.flot'.$suffix.'.js', 'jquery', '1.0' );
|
||||
wp_enqueue_script( 'flot-resize', $woocommerce->plugin_url() . '/assets/js/admin/jquery.flot.resize'.$suffix.'.js', 'jquery', '1.0' );
|
||||
wp_enqueue_script( 'flot-stack', $woocommerce->plugin_url() . '/assets/js/admin/jquery.flot.stack'.$suffix.'.js', 'jquery', '1.0' );
|
||||
wp_enqueue_script( 'flot-threshold', $woocommerce->plugin_url() . '/assets/js/admin/jquery.flot.threshold'.$suffix.'.js', 'jquery', '1.0' );
|
||||
|
||||
endif;
|
||||
|
|
|
@ -45,7 +45,13 @@ function woocommerce_reports() {
|
|||
'function' => 'woocommerce_top_earners'
|
||||
)
|
||||
),
|
||||
__('customers', 'woothemes') => array()
|
||||
__('customers', 'woothemes') => array(
|
||||
array(
|
||||
'title' => __('Overview', 'woothemes'),
|
||||
'description' => '',
|
||||
'function' => 'woocommerce_customer_overview'
|
||||
),
|
||||
)
|
||||
);
|
||||
?>
|
||||
<div class="wrap woocommerce">
|
||||
|
@ -60,7 +66,7 @@ function woocommerce_reports() {
|
|||
<?php do_action('woocommerce_reports_tabs'); ?>
|
||||
</h2>
|
||||
|
||||
<ul class="subsubsub"><li><?php
|
||||
<?php if (sizeof($charts[$current_tab])>1) : ?><ul class="subsubsub"><li><?php
|
||||
$links = array();
|
||||
foreach ($charts[$current_tab] as $key => $chart) :
|
||||
$link = '<a href="admin.php?page=woocommerce_reports&tab='.$current_tab.'&chart='.$key.'" class="';
|
||||
|
@ -69,11 +75,12 @@ function woocommerce_reports() {
|
|||
$links[] = $link;
|
||||
endforeach;
|
||||
echo implode(' | </li><li>', $links);
|
||||
?></li></ul><br class="clear" />
|
||||
?></li></ul><br class="clear" /><?php endif; ?>
|
||||
|
||||
<?php if (isset($charts[$current_tab][$current_chart])) : ?>
|
||||
<h3><?php echo $charts[$current_tab][$current_chart]['title']; ?></h3>
|
||||
<?php if (sizeof($charts[$current_tab])>1) : ?><h3><?php echo $charts[$current_tab][$current_chart]['title']; ?></h3>
|
||||
<?php if ($charts[$current_tab][$current_chart]['description']) : ?><p><?php echo $charts[$current_tab][$current_chart]['description']; ?></p><?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
$func = $charts[$current_tab][$current_chart]['function'];
|
||||
if ($func && function_exists($func)) $func();
|
||||
|
@ -1117,3 +1124,184 @@ function woocommerce_product_sales() {
|
|||
<?php
|
||||
endif;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Customer overview
|
||||
*/
|
||||
function woocommerce_customer_overview() {
|
||||
|
||||
global $start_date, $end_date, $woocommerce, $wpdb;
|
||||
|
||||
$total_customers = 0;
|
||||
$total_customer_sales = 0;
|
||||
$total_guest_sales = 0;
|
||||
$total_customer_orders = 0;
|
||||
$total_guest_orders = 0;
|
||||
|
||||
$users_query = new WP_User_Query( array(
|
||||
'fields' => array('user_registered'),
|
||||
'role' => 'customer'
|
||||
) );
|
||||
$customers = $users_query->get_results();
|
||||
$total_customers = (int) sizeof($customers);
|
||||
|
||||
$args = array(
|
||||
'numberposts' => -1,
|
||||
'orderby' => 'post_date',
|
||||
'order' => 'DESC',
|
||||
'post_type' => 'shop_order',
|
||||
'post_status' => 'publish' ,
|
||||
'suppress_filters' => false,
|
||||
'tax_query' => array(
|
||||
array(
|
||||
'taxonomy' => 'shop_order_status',
|
||||
'terms' => array('completed', 'processing', 'on-hold'),
|
||||
'field' => 'slug',
|
||||
'operator' => 'IN'
|
||||
)
|
||||
)
|
||||
);
|
||||
$orders = get_posts( $args );
|
||||
foreach ($orders as $order) :
|
||||
if (get_post_meta( $order->ID, '_customer_user', true )>0) :
|
||||
$total_customer_sales += get_post_meta($order->ID, '_order_total', true);
|
||||
$total_customer_orders++;
|
||||
else :
|
||||
$total_guest_sales += get_post_meta($order->ID, '_order_total', true);
|
||||
$total_guest_orders++;
|
||||
endif;
|
||||
endforeach;
|
||||
|
||||
?>
|
||||
<div id="poststuff" class="woocommerce-reports-wrap">
|
||||
<div class="woocommerce-reports-sidebar">
|
||||
<div class="postbox">
|
||||
<h3><span><?php _e('Total customers', 'woothemes'); ?></span></h3>
|
||||
<div class="inside">
|
||||
<p class="stat"><?php if ($total_customers>0) echo $total_customers; else _e('n/a', 'woothemes'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="postbox">
|
||||
<h3><span><?php _e('Total customer sales', 'woothemes'); ?></span></h3>
|
||||
<div class="inside">
|
||||
<p class="stat"><?php if ($total_customer_sales>0) echo woocommerce_price($total_customer_sales); else _e('n/a', 'woothemes'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="postbox">
|
||||
<h3><span><?php _e('Total guest sales', 'woothemes'); ?></span></h3>
|
||||
<div class="inside">
|
||||
<p class="stat"><?php if ($total_guest_sales>0) echo woocommerce_price($total_guest_sales); else _e('n/a', 'woothemes'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="postbox">
|
||||
<h3><span><?php _e('Total customer orders', 'woothemes'); ?></span></h3>
|
||||
<div class="inside">
|
||||
<p class="stat"><?php if ($total_customer_orders>0) echo $total_customer_orders; else _e('n/a', 'woothemes'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="postbox">
|
||||
<h3><span><?php _e('Total guest orders', 'woothemes'); ?></span></h3>
|
||||
<div class="inside">
|
||||
<p class="stat"><?php if ($total_guest_orders>0) echo $total_guest_orders; else _e('n/a', 'woothemes'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="postbox">
|
||||
<h3><span><?php _e('Average orders per customer', 'woothemes'); ?></span></h3>
|
||||
<div class="inside">
|
||||
<p class="stat"><?php if ($total_customer_orders>0 && $total_customers>0) echo number_format($total_customer_orders/$total_customers, 2); else _e('n/a', 'woothemes'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="woocommerce-reports-main">
|
||||
<div class="postbox">
|
||||
<h3><span><?php _e('Signups per day', 'woothemes'); ?></span></h3>
|
||||
<div class="inside chart">
|
||||
<div id="placeholder" style="width:100%; overflow:hidden; height:568px; position:relative;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
$start_date = strtotime('-30 days');
|
||||
$end_date = strtotime('NOW');
|
||||
$signups = array();
|
||||
|
||||
// Blank date ranges to begin
|
||||
$count = 0;
|
||||
$days = ($end_date - $start_date) / (60 * 60 * 24);
|
||||
|
||||
while ($count < $days) :
|
||||
$time = strtotime(date('Ymd', strtotime('+ '.$count.' DAY', $start_date))).'000';
|
||||
|
||||
$signups[$time] = 0;
|
||||
|
||||
$count++;
|
||||
endwhile;
|
||||
|
||||
|
||||
|
||||
foreach ($customers as $customer) :
|
||||
if (strtotime($customer->user_registered) > $start_date) :
|
||||
$time = strtotime(date('Ymd', strtotime($customer->user_registered))).'000';
|
||||
|
||||
if (isset($signups[$time])) :
|
||||
$signups[$time]++;
|
||||
else :
|
||||
$signups[$time] = 1;
|
||||
endif;
|
||||
endif;
|
||||
endforeach;
|
||||
|
||||
$signups_array = array();
|
||||
foreach ($signups as $key => $count) :
|
||||
$signups_array[] = array($key, $count);
|
||||
endforeach;
|
||||
|
||||
$chart_data = json_encode($signups_array);
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
jQuery(function(){
|
||||
var d = jQuery.parseJSON( '<?php echo $chart_data; ?>' );
|
||||
|
||||
for (var i = 0; i < d.length; ++i) d[i][0] += 60 * 60 * 1000;
|
||||
|
||||
var placeholder = jQuery("#placeholder");
|
||||
|
||||
var plot = jQuery.plot(placeholder, [ { data: d } ], {
|
||||
series: {
|
||||
bars: {
|
||||
barWidth: 60 * 60 * 24 * 1000,
|
||||
align: "center",
|
||||
show: true
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
aboveData: false,
|
||||
color: '#ccc',
|
||||
backgroundColor: '#fff',
|
||||
borderWidth: 2,
|
||||
borderColor: '#ccc',
|
||||
clickable: false,
|
||||
hoverable: true,
|
||||
markings: weekendAreas
|
||||
},
|
||||
xaxis: {
|
||||
mode: "time",
|
||||
timeformat: "%d %b",
|
||||
tickLength: 1,
|
||||
minTickSize: [1, "day"]
|
||||
},
|
||||
yaxes: [ { position: "right", min: 0, tickSize: 1, tickDecimals: 0 } ],
|
||||
colors: ["#8a4b75"]
|
||||
});
|
||||
|
||||
placeholder.resize();
|
||||
|
||||
<?php woocommerce_weekend_area_js(); ?>
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
|
@ -156,7 +156,7 @@ div.multi_select_products_wrapper-alt{float:right;}
|
|||
img.tips{padding:5px 0 0 0;}
|
||||
#easyTooltip{padding:5px 10px;border:3px solid #3da5d5;background:#288ab7;color:#fff;font-size:12px;-moz-border-radius:4px;-webkit-border-radius:4px;-o-border-radius:4px;-khtml-border-radius:4px;border-radius:4px;max-width:300px;}
|
||||
img.ui-datepicker-trigger{vertical-align:middle;margin-top:-1px;cursor:pointer;}
|
||||
.woocommerce-reports-wrap{margin-left:300px;padding-top:10px;}.woocommerce-reports-wrap .postbox h3{cursor:default !important;}
|
||||
.woocommerce-reports-wrap{margin-left:300px;padding-top:18px;}.woocommerce-reports-wrap .postbox h3{cursor:default !important;}
|
||||
.woocommerce-reports-wrap .postbox .stat{font-size:1.5em !important;font-weight:bold;text-align:center;}
|
||||
.woocommerce-reports-wrap .postbox .chart{padding:16px;}
|
||||
.woocommerce-reports-wrap .woocommerce-reports-main{float:left;min-width:100%;}
|
||||
|
|
|
@ -831,7 +831,7 @@ img.ui-datepicker-trigger { vertical-align: middle; margin-top: -1px; cursor: po
|
|||
/* Reports */
|
||||
.woocommerce-reports-wrap {
|
||||
margin-left: 300px;
|
||||
padding-top: 10px;
|
||||
padding-top: 18px;
|
||||
.postbox {
|
||||
h3 {
|
||||
cursor: default !important;
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
/*
|
||||
Flot plugin for stacking data sets, i.e. putting them on top of each
|
||||
other, for accumulative graphs.
|
||||
|
||||
The plugin assumes the data is sorted on x (or y if stacking
|
||||
horizontally). For line charts, it is assumed that if a line has an
|
||||
undefined gap (from a null point), then the line above it should have
|
||||
the same gap - insert zeros instead of "null" if you want another
|
||||
behaviour. This also holds for the start and end of the chart. Note
|
||||
that stacking a mix of positive and negative values in most instances
|
||||
doesn't make sense (so it looks weird).
|
||||
|
||||
Two or more series are stacked when their "stack" attribute is set to
|
||||
the same key (which can be any number or string or just "true"). To
|
||||
specify the default stack, you can set
|
||||
|
||||
series: {
|
||||
stack: null or true or key (number/string)
|
||||
}
|
||||
|
||||
or specify it for a specific series
|
||||
|
||||
$.plot($("#placeholder"), [{ data: [ ... ], stack: true }])
|
||||
|
||||
The stacking order is determined by the order of the data series in
|
||||
the array (later series end up on top of the previous).
|
||||
|
||||
Internally, the plugin modifies the datapoints in each series, adding
|
||||
an offset to the y value. For line series, extra data points are
|
||||
inserted through interpolation. If there's a second y value, it's also
|
||||
adjusted (e.g for bar charts or filled areas).
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
var options = {
|
||||
series: { stack: null } // or number/string
|
||||
};
|
||||
|
||||
function init(plot) {
|
||||
function findMatchingSeries(s, allseries) {
|
||||
var res = null
|
||||
for (var i = 0; i < allseries.length; ++i) {
|
||||
if (s == allseries[i])
|
||||
break;
|
||||
|
||||
if (allseries[i].stack == s.stack)
|
||||
res = allseries[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function stackData(plot, s, datapoints) {
|
||||
if (s.stack == null)
|
||||
return;
|
||||
|
||||
var other = findMatchingSeries(s, plot.getData());
|
||||
if (!other)
|
||||
return;
|
||||
|
||||
var ps = datapoints.pointsize,
|
||||
points = datapoints.points,
|
||||
otherps = other.datapoints.pointsize,
|
||||
otherpoints = other.datapoints.points,
|
||||
newpoints = [],
|
||||
px, py, intery, qx, qy, bottom,
|
||||
withlines = s.lines.show,
|
||||
horizontal = s.bars.horizontal,
|
||||
withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
|
||||
withsteps = withlines && s.lines.steps,
|
||||
fromgap = true,
|
||||
keyOffset = horizontal ? 1 : 0,
|
||||
accumulateOffset = horizontal ? 0 : 1,
|
||||
i = 0, j = 0, l;
|
||||
|
||||
while (true) {
|
||||
if (i >= points.length)
|
||||
break;
|
||||
|
||||
l = newpoints.length;
|
||||
|
||||
if (points[i] == null) {
|
||||
// copy gaps
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
i += ps;
|
||||
}
|
||||
else if (j >= otherpoints.length) {
|
||||
// for lines, we can't use the rest of the points
|
||||
if (!withlines) {
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
}
|
||||
i += ps;
|
||||
}
|
||||
else if (otherpoints[j] == null) {
|
||||
// oops, got a gap
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(null);
|
||||
fromgap = true;
|
||||
j += otherps;
|
||||
}
|
||||
else {
|
||||
// cases where we actually got two points
|
||||
px = points[i + keyOffset];
|
||||
py = points[i + accumulateOffset];
|
||||
qx = otherpoints[j + keyOffset];
|
||||
qy = otherpoints[j + accumulateOffset];
|
||||
bottom = 0;
|
||||
|
||||
if (px == qx) {
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
|
||||
newpoints[l + accumulateOffset] += qy;
|
||||
bottom = qy;
|
||||
|
||||
i += ps;
|
||||
j += otherps;
|
||||
}
|
||||
else if (px > qx) {
|
||||
// we got past point below, might need to
|
||||
// insert interpolated extra point
|
||||
if (withlines && i > 0 && points[i - ps] != null) {
|
||||
intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
|
||||
newpoints.push(qx);
|
||||
newpoints.push(intery + qy);
|
||||
for (m = 2; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
bottom = qy;
|
||||
}
|
||||
|
||||
j += otherps;
|
||||
}
|
||||
else { // px < qx
|
||||
if (fromgap && withlines) {
|
||||
// if we come from a gap, we just skip this point
|
||||
i += ps;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints.push(points[i + m]);
|
||||
|
||||
// we might be able to interpolate a point below,
|
||||
// this can give us a better y
|
||||
if (withlines && j > 0 && otherpoints[j - otherps] != null)
|
||||
bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
|
||||
|
||||
newpoints[l + accumulateOffset] += bottom;
|
||||
|
||||
i += ps;
|
||||
}
|
||||
|
||||
fromgap = false;
|
||||
|
||||
if (l != newpoints.length && withbottom)
|
||||
newpoints[l + 2] += bottom;
|
||||
}
|
||||
|
||||
// maintain the line steps invariant
|
||||
if (withsteps && l != newpoints.length && l > 0
|
||||
&& newpoints[l] != null
|
||||
&& newpoints[l] != newpoints[l - ps]
|
||||
&& newpoints[l + 1] != newpoints[l - ps + 1]) {
|
||||
for (m = 0; m < ps; ++m)
|
||||
newpoints[l + ps + m] = newpoints[l + m];
|
||||
newpoints[l + 1] = newpoints[l - ps + 1];
|
||||
}
|
||||
}
|
||||
|
||||
datapoints.points = newpoints;
|
||||
}
|
||||
|
||||
plot.hooks.processDatapoints.push(stackData);
|
||||
}
|
||||
|
||||
$.plot.plugins.push({
|
||||
init: init,
|
||||
options: options,
|
||||
name: 'stack',
|
||||
version: '1.2'
|
||||
});
|
||||
})(jQuery);
|
|
@ -1 +0,0 @@
|
|||
(function(b){var a={series:{stack:null}};function c(f){function d(k,j){var h=null;for(var g=0;g<j.length;++g){if(k==j[g]){break}if(j[g].stack==k.stack){h=j[g]}}return h}function e(C,v,g){if(v.stack==null){return}var p=d(v,C.getData());if(!p){return}var z=g.pointsize,F=g.points,h=p.datapoints.pointsize,y=p.datapoints.points,t=[],x,w,k,J,I,r,u=v.lines.show,G=v.bars.horizontal,o=z>2&&(G?g.format[2].x:g.format[2].y),n=u&&v.lines.steps,E=true,q=G?1:0,H=G?0:1,D=0,B=0,A;while(true){if(D>=F.length){break}A=t.length;if(F[D]==null){for(m=0;m<z;++m){t.push(F[D+m])}D+=z}else{if(B>=y.length){if(!u){for(m=0;m<z;++m){t.push(F[D+m])}}D+=z}else{if(y[B]==null){for(m=0;m<z;++m){t.push(null)}E=true;B+=h}else{x=F[D+q];w=F[D+H];J=y[B+q];I=y[B+H];r=0;if(x==J){for(m=0;m<z;++m){t.push(F[D+m])}t[A+H]+=I;r=I;D+=z;B+=h}else{if(x>J){if(u&&D>0&&F[D-z]!=null){k=w+(F[D-z+H]-w)*(J-x)/(F[D-z+q]-x);t.push(J);t.push(k+I);for(m=2;m<z;++m){t.push(F[D+m])}r=I}B+=h}else{if(E&&u){D+=z;continue}for(m=0;m<z;++m){t.push(F[D+m])}if(u&&B>0&&y[B-h]!=null){r=I+(y[B-h+H]-I)*(x-J)/(y[B-h+q]-J)}t[A+H]+=r;D+=z}}E=false;if(A!=t.length&&o){t[A+2]+=r}}}}if(n&&A!=t.length&&A>0&&t[A]!=null&&t[A]!=t[A-z]&&t[A+1]!=t[A-z+1]){for(m=0;m<z;++m){t[A+z+m]=t[A+m]}t[A+1]=t[A-z+1]}}g.points=t}f.hooks.processDatapoints.push(e)}b.plot.plugins.push({init:c,options:a,name:"stack",version:"1.2"})})(jQuery);
|
Loading…
Reference in New Issue