customer reports

This commit is contained in:
Mike Jolley 2011-09-17 01:12:25 +01:00
parent 87e3d83b67
commit abe07eb787
6 changed files with 194 additions and 192 deletions

View File

@ -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;

View File

@ -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.'&amp;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
}

View File

@ -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%;}

View File

@ -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;

View File

@ -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);

View File

@ -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);