Moves product stat view logic to serializer
- Split frontend into multiple files - Bump frontend deps - Remove unused stuff
No files matched your search
@@ -1,7 +1,41 @@
|
||||
from main.models import Product
|
||||
from django.db.models import Sum
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
|
||||
from main.models import Product, OrderLine
|
||||
|
||||
|
||||
class ProductSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Product
|
||||
|
||||
|
||||
class ProductStatSerializer(ModelSerializer):
|
||||
EXTERNAL_USER = 'Ekstern'
|
||||
counts = serializers.SerializerMethodField()
|
||||
|
||||
def _format_counts(self, per_user):
|
||||
per_user = list(per_user)
|
||||
for row in per_user:
|
||||
first = row.pop('order__customer__first_name')
|
||||
last = row.pop('order__customer__last_name')
|
||||
if first is None:
|
||||
who = self.EXTERNAL_USER
|
||||
else:
|
||||
who = '{} {}'.format(first, last)
|
||||
row['full_name'] = who
|
||||
|
||||
return per_user
|
||||
|
||||
def get_counts(self, obj):
|
||||
per_user = OrderLine.objects\
|
||||
.filter(product=obj)\
|
||||
.values('order__customer__first_name', 'order__customer__last_name')\
|
||||
.annotate(amount_total=Sum('amount'))\
|
||||
.order_by('-amount_total')
|
||||
|
||||
return self._format_counts(per_user)
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = ['id', 'name', 'description', 'image', 'inventory_amount', 'active', 'created', 'counts']
|
||||
@@ -1,9 +1,18 @@
|
||||
from main.api.serializers import ProductSerializer
|
||||
from main.models import Product
|
||||
from rest_framework.decorators import list_route
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from main.api.serializers import ProductSerializer, ProductStatSerializer
|
||||
from main.models import Product
|
||||
|
||||
|
||||
class ProductViewSet(ModelViewSet):
|
||||
# TODO: auth
|
||||
serializer_class = ProductSerializer
|
||||
queryset = Product.objects.all()
|
||||
|
||||
@list_route(methods=['get'])
|
||||
def group_by_user(self, request):
|
||||
products = self.get_queryset().filter(active=True)
|
||||
serializer = ProductStatSerializer(products, many=True)
|
||||
|
||||
return Response(serializer.data)
|
||||
@@ -1,7 +1,6 @@
|
||||
from django.conf.urls import url
|
||||
from main.api.views import ProductViewSet
|
||||
from main.views.stats import stats_list, stats_orders, stats_orders_hourly, stats_products_realtime, stats_products, \
|
||||
stats_products_per_user, products_json
|
||||
from main.views.stats import stats_list, stats_orders, stats_orders_hourly, stats_products_realtime, stats_products_per_user
|
||||
from main.views.general import register, deposit, log
|
||||
from main.views.reports import inventory, inventory_add, report
|
||||
from main.views.users import create_user, profile
|
||||
@@ -18,12 +17,7 @@ urlpatterns = [
|
||||
url(r'^stats/orders/$', stats_orders, name='stats-orders'),
|
||||
url(r'^stats/orders/hourly$', stats_orders_hourly, name='stats-orders-hourly'),
|
||||
url(r'^stats/orders/products_realtime$', stats_products_realtime, name='stats-products-realtime'),
|
||||
url(r'^stats/products/$', stats_products, name='stats-products'),
|
||||
url(r'^stats/products/(?P<product>[0-9]+)/$', stats_products, name='stats-product'),
|
||||
url(r'^stats/products/by_user/(?P<user_id>[0-9]+)/$', stats_products_per_user, name='stats-products-per-user'),
|
||||
# Products
|
||||
url(r'^products/(?P<product_id>[0-9]+)/$', products_json, name='json-product'),
|
||||
url(r'^products/$', products_json, name='json-products'),
|
||||
# Users
|
||||
url(r'^user/create/$', create_user, name='create-user'),
|
||||
url(r'^profile/$', profile, name='profile'),
|
||||
|
||||
@@ -16,82 +16,6 @@ def stats_list(request):
|
||||
return render(request, 'stats.html', {'products': products})
|
||||
|
||||
|
||||
def serialize_product(product):
|
||||
f_product = {}
|
||||
for attr in Product._meta.get_all_field_names():
|
||||
if attr in ["transactions", "orderlines", "image"]:
|
||||
continue
|
||||
|
||||
if hasattr(product, attr) and product.__getattribute__(attr):
|
||||
if type(product.__getattribute__(attr)) is datetime:
|
||||
f_product[attr] = str(product.__getattribute__(attr))
|
||||
else:
|
||||
f_product[attr] = product.__getattribute__(attr)
|
||||
return f_product
|
||||
|
||||
|
||||
def products_json(request, product_id=None):
|
||||
if product_id:
|
||||
# single
|
||||
products = get_object_or_404(Product, pk=product_id)
|
||||
else:
|
||||
# many
|
||||
products = Product.objects.filter(active=True)
|
||||
|
||||
if product_id:
|
||||
f_products = serialize_product(products)
|
||||
else:
|
||||
f_products = [serialize_product(product) for product in products]
|
||||
|
||||
return JsonResponse(f_products)
|
||||
|
||||
|
||||
def stats_product(product):
|
||||
# reverse lookup via order, customer
|
||||
per_user = OrderLine.objects\
|
||||
.filter(product=product)\
|
||||
.values('order__customer__first_name', 'order__customer__last_name')\
|
||||
.annotate(num=Sum('amount'))\
|
||||
.order_by('-num')
|
||||
f_per_user = []
|
||||
for row in per_user:
|
||||
if row['order__customer__first_name'] is not None:
|
||||
who = row['order__customer__first_name'] + " " + row['order__customer__last_name']
|
||||
else:
|
||||
who = "Ekstern"
|
||||
num = row['num']
|
||||
f_per_user.append([who, num])
|
||||
|
||||
response = {
|
||||
'product': serialize_product(Product.objects.get(pk=product)) if type(product) in six.string_types else serialize_product(product),
|
||||
'counts': f_per_user,
|
||||
'total_counts': sum([row['num'] for row in per_user]),
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def stats_products(request, product=None):
|
||||
if product:
|
||||
# single
|
||||
f_products = [stats_product(product)]
|
||||
sales = OrderLine.objects.filter(product=product)
|
||||
else:
|
||||
# all
|
||||
sales = OrderLine.objects.filter(product__active=True)
|
||||
f_products = [stats_product(p) for p in Product.objects.filter(active=True)]
|
||||
|
||||
totals = float(sum([sale.amount for sale in sales]))
|
||||
response = {
|
||||
'request': 'all' if not product else product,
|
||||
'response': {
|
||||
'products': f_products,
|
||||
'total_count': totals,
|
||||
}
|
||||
}
|
||||
return JsonResponse(response)
|
||||
|
||||
|
||||
def stats_orders(request):
|
||||
num_external_orders = Order.objects.filter(customer=None).count()
|
||||
per_user = Order.objects\
|
||||
|
||||
@@ -6,4 +6,5 @@ django-widget-tweaks==1.3
|
||||
facebook-sdk==0.4.0
|
||||
gunicorn==19.3.0
|
||||
mysqlclient==1.3.6
|
||||
djangorestframework==3.3.1
|
||||
Pillow==3.0.0
|
||||
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
@@ -1,447 +0,0 @@
|
||||
function enable_all_customers() {
|
||||
$("li.customer").each(function(i) {
|
||||
$(this).removeClass("marked");
|
||||
$(this).show();
|
||||
});
|
||||
}
|
||||
function filter_customers(typed) {
|
||||
setTimeout(function() {
|
||||
$("li.customer").each(function(i) {
|
||||
var pattern = new RegExp(typed,"i");
|
||||
var name = $(this).children("span.name").text();
|
||||
if(!pattern.test(name)) {
|
||||
$(this).hide();
|
||||
$(this).removeClass("marked");
|
||||
} else {
|
||||
$(this).show();
|
||||
var full_match = name.toLowerCase() == typed.toLowerCase();
|
||||
// FIXME: needs one more char.
|
||||
var single_match = $("li.customer:visible").length == 1;
|
||||
console.log(full_match, single_match);
|
||||
//if(full_match || single_match) {
|
||||
if(full_match) {
|
||||
$(this).addClass("marked");
|
||||
} else {
|
||||
$(this).removeClass("marked");
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
function enable_all_products() {
|
||||
$("li.product").each(function(i) {
|
||||
$(this).removeClass("marked");
|
||||
$(this).show();
|
||||
});
|
||||
}
|
||||
function reset_qtys() {
|
||||
$(".qty").each(function(i) {
|
||||
$(this).html('0');
|
||||
});
|
||||
|
||||
$("input[name$=amount]").each(function(i) {
|
||||
$(this).val("0");
|
||||
});
|
||||
}
|
||||
function update_total() {
|
||||
var total = 0;
|
||||
$('li.product').each(function() {
|
||||
var amount = Number($(this).find('[name$=amount]').val());
|
||||
|
||||
var price_selector = '.sale_price_int';
|
||||
if($("#id_customer_typeahead").val() === "Ekstern") {
|
||||
price_selector = '.sale_price_ext';
|
||||
}
|
||||
var sale_price = Number($(this).find(price_selector).text());
|
||||
|
||||
total += amount * sale_price;
|
||||
});
|
||||
if(total === 0) {
|
||||
total = "";
|
||||
}
|
||||
$("span.total_text").html(total);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
/* Hide alert messages */
|
||||
$("a[data-dismiss=alert]").click(function(){
|
||||
$(this).parent().parent().parent().slideUp();
|
||||
});
|
||||
setTimeout( function() {
|
||||
$("a[data-dismiss=alert]").parent().parent().parent().slideUp();
|
||||
}, 5000);
|
||||
|
||||
/* Tooltips */
|
||||
$("h2").tooltip({placement: 'right'});
|
||||
$("img.customer").tooltip();
|
||||
$("a.product").tooltip();
|
||||
$("#footer .credits .love").tooltip();
|
||||
|
||||
/*
|
||||
* View: Deposit and register
|
||||
*/
|
||||
|
||||
/* Customer search */
|
||||
$('#id_customer_typeahead').keyup(function(event) {
|
||||
filter_customers(event.target.value);
|
||||
});
|
||||
/* Customer selection */
|
||||
$("li.customer").click( function() {
|
||||
enable_all_customers();
|
||||
/* Disable the customer button just clicked */
|
||||
$(this).addClass("marked");
|
||||
/* Update the typeahead field */
|
||||
var name = $(this).children("span.name").text();
|
||||
$("#id_customer_typeahead").val(name);
|
||||
/* Update the select field */
|
||||
var id = $(this).children("span.id").text();
|
||||
$("#id_customer option, #id_user option").removeAttr('selected');
|
||||
if(id === "") {
|
||||
// External user
|
||||
$("#id_customer option:first-child, #id_user option:first-child").attr('selected','selected');
|
||||
} else {
|
||||
$("#id_customer option[value="+id+"], #id_user option[value="+id+"]").attr('selected','selected');
|
||||
}
|
||||
update_total(this);
|
||||
});
|
||||
|
||||
/* Set Global Highcharts options */
|
||||
|
||||
Highcharts.setOptions({
|
||||
credits: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* View: Register
|
||||
*/
|
||||
|
||||
/* Product amount controls */
|
||||
$("#register li.product").click( function() {
|
||||
/* Mark the product as selected */
|
||||
$(this).addClass("marked");
|
||||
/* Update amounts */
|
||||
var form_field = $(this).children("input[name$=amount]");
|
||||
var display = $(this).children(".qty");
|
||||
var qty = Number(form_field.val()) + 1;
|
||||
form_field.val(qty);
|
||||
display.html(qty);
|
||||
/* Update total*/
|
||||
update_total(this);
|
||||
});
|
||||
/* Form reset */
|
||||
$("#register button[type=reset]").click( function() {
|
||||
$('#id_customer option').removeAttr('selected');
|
||||
$("#id_customer_typeahead").val("");
|
||||
$("span.total_text").html("");
|
||||
enable_all_customers();
|
||||
enable_all_products();
|
||||
reset_qtys();
|
||||
});
|
||||
|
||||
/* Toggle infrequent users */
|
||||
$(".toggle-infrequent-users").on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$("li.customer.infrequent").toggleClass('hidden');
|
||||
$(".users-description .infrequent-users").toggleClass('hidden');
|
||||
if($(this).text() === "Vis alle") {
|
||||
$(this).text("Skjul gamliser");
|
||||
}
|
||||
else {
|
||||
$(this).text("Vis alle");
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* View: Stats
|
||||
*/
|
||||
if( $("#stats").length > 0) {
|
||||
/* TODO select data element with index 0 of each pie-chart */
|
||||
var charts = [];
|
||||
var products_url = '/stats/products/';
|
||||
var order_url = '/stats/orders/';
|
||||
var order_hourly_url = '/stats/orders/hourly';
|
||||
var products_realtime_url = '/stats/orders/products_realtime';
|
||||
|
||||
/* orders */
|
||||
$.getJSON(order_url, function(data) {
|
||||
var render_to = 'order_chart';
|
||||
|
||||
// define the options
|
||||
var options = {
|
||||
chart: {
|
||||
renderTo: render_to,
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
plotShadow: false
|
||||
},
|
||||
title: {
|
||||
text: 'Fordeling av ordre på person'
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function() {
|
||||
return '<b>'+ this.point.name +'</b>: '+ Highcharts.numberFormat(this.percentage, 1) +' %';
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
color: '#000000',
|
||||
connectorColor: '#000000',
|
||||
formatter: function() {
|
||||
return '<b>'+ this.point.name +'</b>: '+ this.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'pie',
|
||||
name: 'Orders',
|
||||
data: data
|
||||
}]
|
||||
};
|
||||
charts.push(new Highcharts.Chart(options));
|
||||
});
|
||||
|
||||
/* products */
|
||||
$.getJSON(products_url, function(data) {
|
||||
var products = data.response.products;
|
||||
for(var i in products) {
|
||||
var render_to = 'product_chart' + products[i].product.id;
|
||||
var series_data = products[i].counts;
|
||||
// define the options
|
||||
var options = {
|
||||
chart: {
|
||||
renderTo: render_to,
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
plotShadow: false
|
||||
},
|
||||
title: {
|
||||
text: products[i].product.name
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function() {
|
||||
return '<b>'+ this.point.name +'</b>: '+ Highcharts.numberFormat(this.percentage, 1) +' %';
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
color: '#000000',
|
||||
connectorColor: '#000000',
|
||||
formatter: function() {
|
||||
return '<b>'+ this.point.name +'</b>: ' + this.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'pie',
|
||||
name: 'Orders',
|
||||
data: series_data
|
||||
}]
|
||||
};
|
||||
charts.push(new Highcharts.Chart(options));
|
||||
}
|
||||
});
|
||||
/* orders hourly */
|
||||
$.getJSON(order_hourly_url, function(data) {
|
||||
var render_to = 'order_hourly_chart';
|
||||
|
||||
// define the options
|
||||
var options = {
|
||||
chart: {
|
||||
renderTo: render_to,
|
||||
type: 'column'
|
||||
},
|
||||
title: {
|
||||
text: 'Ordre - time for time'
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function() {
|
||||
return this.y + " stk";
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: 'Ordre'
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
title: {
|
||||
text: 'Tid (h)'
|
||||
},
|
||||
labels: {
|
||||
formatter: function() {
|
||||
return this.value + ":00";
|
||||
},
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
column: {
|
||||
minPointLength: 3,
|
||||
},
|
||||
series: {
|
||||
groupPadding: 0,
|
||||
pointPadding: 0.05,
|
||||
},
|
||||
},
|
||||
series: [{
|
||||
name: 'Ordre',
|
||||
data: data.hourly
|
||||
}]
|
||||
};
|
||||
charts.push(new Highcharts.Chart(options));
|
||||
});
|
||||
|
||||
var products_realtime_chart = new Highcharts.Chart({
|
||||
chart: {
|
||||
renderTo: 'products_realtime_chart'
|
||||
},
|
||||
title: {
|
||||
text: "Produkter solgt over tid"
|
||||
},
|
||||
xAxis: {
|
||||
type: "datetime"
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: "Antall solgt"
|
||||
}
|
||||
},
|
||||
|
||||
plotOptions: {
|
||||
series: {
|
||||
marker: {
|
||||
radius: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
series: {}
|
||||
});
|
||||
charts.push(products_realtime_chart);
|
||||
|
||||
function parse_products_realtime_data(data) {
|
||||
var chart = products_realtime_chart;
|
||||
|
||||
for (var product in data.products) {
|
||||
if (chart.get(product) === null) {
|
||||
chart.addSeries({
|
||||
data: [],
|
||||
id: product,
|
||||
name: product
|
||||
});
|
||||
|
||||
var series = chart.get(product);
|
||||
_.each(data.products[product], function(point) {
|
||||
series.addPoint({
|
||||
id: point[0],
|
||||
x: point[0],
|
||||
y: point[1]
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
var series = chart.get(product);
|
||||
_.each(data.products[product], function(point) {
|
||||
if (chart.get(point[0]) === null) {
|
||||
series.addPoint({
|
||||
id: point[0],
|
||||
x: point[0],
|
||||
y: point[1]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_products_realtime_data() {
|
||||
var that = this;
|
||||
$.ajax( {
|
||||
url: products_realtime_url,
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
parse_products_realtime_data(data);
|
||||
|
||||
setTimeout(function () {
|
||||
get_products_realtime_data();
|
||||
}, 15000);
|
||||
},
|
||||
cache: false
|
||||
});
|
||||
}
|
||||
|
||||
get_products_realtime_data();
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* View: Inventory report
|
||||
*/
|
||||
var start_field = $(".inventory-report-form #id_start_time");
|
||||
start_field.datepicker({
|
||||
language: 'nb',
|
||||
format: 'yyyy-mm-dd',
|
||||
todayHighlight: true,
|
||||
weekStart: 1,
|
||||
endDate: new Date()
|
||||
}).on('changeDate', function(e) {
|
||||
end_field.datepicker('setStartDate', e.date);
|
||||
});
|
||||
var end_field = $(".inventory-report-form #id_end_time");
|
||||
end_field.datepicker({
|
||||
language: 'nb',
|
||||
format: 'yyyy-mm-dd',
|
||||
todayHighlight: true,
|
||||
weekStart: 1,
|
||||
endDate: new Date()
|
||||
}).on('changeDate', function(e) {
|
||||
start_field.datepicker('setEndDate', e.date);
|
||||
});
|
||||
|
||||
/*
|
||||
* View: Profile
|
||||
*/
|
||||
if( $('#user-profile').length ) {
|
||||
var products_by_user_url = '/stats/products/by_user/';
|
||||
var user_id = $('#user-profile').attr('data-user-id');
|
||||
|
||||
$.getJSON(products_by_user_url + user_id, function(data) {
|
||||
if(data.hasOwnProperty('error')) {
|
||||
return;
|
||||
}
|
||||
$(".profile-stats-orderline").highcharts({
|
||||
title: {
|
||||
text: false
|
||||
},
|
||||
xAxis: {
|
||||
type: "datetime"
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: "Kjøp"
|
||||
}
|
||||
},
|
||||
|
||||
plotOptions: {
|
||||
series: {
|
||||
marker: {
|
||||
radius: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
series: data
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
function enable_all_customers() {
|
||||
$("li.customer").each(function(i) {
|
||||
$(this).removeClass("marked");
|
||||
$(this).show();
|
||||
});
|
||||
}
|
||||
function filter_customers(typed) {
|
||||
setTimeout(function() {
|
||||
$("li.customer").each(function(i) {
|
||||
var pattern = new RegExp(typed,"i");
|
||||
var name = $(this).children("span.name").text();
|
||||
if(!pattern.test(name)) {
|
||||
$(this).hide();
|
||||
$(this).removeClass("marked");
|
||||
} else {
|
||||
$(this).show();
|
||||
var full_match = name.toLowerCase() == typed.toLowerCase();
|
||||
// FIXME: needs one more char.
|
||||
var single_match = $("li.customer:visible").length == 1;
|
||||
console.log(full_match, single_match);
|
||||
//if(full_match || single_match) {
|
||||
if(full_match) {
|
||||
$(this).addClass("marked");
|
||||
} else {
|
||||
$(this).removeClass("marked");
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
function enable_all_products() {
|
||||
$("li.product").each(function(i) {
|
||||
$(this).removeClass("marked");
|
||||
$(this).show();
|
||||
});
|
||||
}
|
||||
function reset_qtys() {
|
||||
$(".qty").each(function(i) {
|
||||
$(this).html('0');
|
||||
});
|
||||
|
||||
$("input[name$=amount]").each(function(i) {
|
||||
$(this).val("0");
|
||||
});
|
||||
}
|
||||
function update_total() {
|
||||
var total = 0;
|
||||
$('li.product').each(function() {
|
||||
var amount = Number($(this).find('[name$=amount]').val());
|
||||
|
||||
var price_selector = '.sale_price_int';
|
||||
if($("#id_customer_typeahead").val() === "Ekstern") {
|
||||
price_selector = '.sale_price_ext';
|
||||
}
|
||||
var sale_price = Number($(this).find(price_selector).text());
|
||||
|
||||
total += amount * sale_price;
|
||||
});
|
||||
if(total === 0) {
|
||||
total = "";
|
||||
}
|
||||
$("span.total_text").html(total);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var $alerts = $("a[data-dismiss=alert]");
|
||||
/* Hide alert messages */
|
||||
$alerts.click(function(){
|
||||
$(this).parent().parent().parent().slideUp();
|
||||
});
|
||||
setTimeout( function() {
|
||||
$alerts.parent().parent().parent().slideUp();
|
||||
}, 5000);
|
||||
|
||||
/* Tooltips */
|
||||
$("h2").tooltip({placement: 'right'});
|
||||
$("img.customer").tooltip();
|
||||
$("a.product").tooltip();
|
||||
$(".love").tooltip();
|
||||
|
||||
/*
|
||||
* View: Deposit and register
|
||||
*/
|
||||
|
||||
/* Customer search */
|
||||
$('#id_customer_typeahead').keyup(function(event) {
|
||||
filter_customers(event.target.value);
|
||||
});
|
||||
/* Customer selection */
|
||||
$("li.customer").click( function() {
|
||||
enable_all_customers();
|
||||
/* Disable the customer button just clicked */
|
||||
$(this).addClass("marked");
|
||||
/* Update the typeahead field */
|
||||
var name = $(this).children("span.name").text();
|
||||
$("#id_customer_typeahead").val(name);
|
||||
/* Update the select field */
|
||||
var id = $(this).children("span.id").text();
|
||||
$("#id_customer option, #id_user option").removeAttr('selected');
|
||||
if(id === "") {
|
||||
// External user
|
||||
$("#id_customer option:first-child, #id_user option:first-child").attr('selected','selected');
|
||||
} else {
|
||||
$("#id_customer option[value="+id+"], #id_user option[value="+id+"]").attr('selected','selected');
|
||||
}
|
||||
update_total(this);
|
||||
});
|
||||
|
||||
/* Set Global Highcharts options */
|
||||
|
||||
Highcharts.setOptions({
|
||||
credits: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* View: Register
|
||||
*/
|
||||
|
||||
/* Product amount controls */
|
||||
$("#register li.product").click( function() {
|
||||
/* Mark the product as selected */
|
||||
$(this).addClass("marked");
|
||||
/* Update amounts */
|
||||
var form_field = $(this).children("input[name$=amount]");
|
||||
var display = $(this).children(".qty");
|
||||
var qty = Number(form_field.val()) + 1;
|
||||
form_field.val(qty);
|
||||
display.html(qty);
|
||||
/* Update total*/
|
||||
update_total(this);
|
||||
});
|
||||
/* Form reset */
|
||||
$("#register button[type=reset]").click( function() {
|
||||
$('#id_customer option').removeAttr('selected');
|
||||
$("#id_customer_typeahead").val("");
|
||||
$("span.total_text").html("");
|
||||
enable_all_customers();
|
||||
enable_all_products();
|
||||
reset_qtys();
|
||||
});
|
||||
|
||||
/* Toggle infrequent users */
|
||||
$(".toggle-infrequent-users").on('click', function(e) {
|
||||
e.preventDefault();
|
||||
$("li.customer.infrequent").toggleClass('hidden');
|
||||
$(".users-description .infrequent-users").toggleClass('hidden');
|
||||
if($(this).text() === "Vis alle") {
|
||||
$(this).text("Skjul gamliser");
|
||||
}
|
||||
else {
|
||||
$(this).text("Vis alle");
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* View: Inventory report
|
||||
*/
|
||||
var start_field = $(".inventory-report-form #id_start_time");
|
||||
start_field.datepicker({
|
||||
language: 'nb',
|
||||
format: 'yyyy-mm-dd',
|
||||
todayHighlight: true,
|
||||
weekStart: 1,
|
||||
endDate: new Date()
|
||||
}).on('changeDate', function(e) {
|
||||
end_field.datepicker('setStartDate', e.date);
|
||||
});
|
||||
var end_field = $(".inventory-report-form #id_end_time");
|
||||
end_field.datepicker({
|
||||
language: 'nb',
|
||||
format: 'yyyy-mm-dd',
|
||||
todayHighlight: true,
|
||||
weekStart: 1,
|
||||
endDate: new Date()
|
||||
}).on('changeDate', function(e) {
|
||||
start_field.datepicker('setEndDate', e.date);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
/* View: Profile */
|
||||
|
||||
$(document).ready(function() {
|
||||
var $userProfile = $('#user-profile');
|
||||
if ($userProfile.length) {
|
||||
var products_by_user_url = '/stats/products/by_user/';
|
||||
var user_id = $('#user-profile').attr('data-user-id');
|
||||
|
||||
$.getJSON(products_by_user_url + user_id, function (data) {
|
||||
if (data.hasOwnProperty('error')) {
|
||||
return;
|
||||
}
|
||||
$(".profile-stats-orderline").highcharts({
|
||||
title: {
|
||||
text: false
|
||||
},
|
||||
xAxis: {
|
||||
type: "datetime"
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: "Kjøp"
|
||||
}
|
||||
},
|
||||
|
||||
plotOptions: {
|
||||
series: {
|
||||
marker: {
|
||||
radius: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
series: data
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,225 @@
|
||||
|
||||
/* View: Stats */
|
||||
|
||||
var products = null;
|
||||
var products_realtime_chart = null;
|
||||
var products_url = '/api/products/';
|
||||
var products_stat_url = '/api/products/group_by_user';
|
||||
|
||||
var order_url = '/stats/orders/';
|
||||
var order_hourly_url = '/stats/orders/hourly';
|
||||
var products_realtime_url = '/stats/orders/products_realtime';
|
||||
|
||||
function productFormatter() {
|
||||
return '<b>'+ this.point.name +'</b>: '+ Highcharts.numberFormat(this.percentage, 1) +' %';
|
||||
}
|
||||
function parse_products_realtime_data(data, chart) {
|
||||
for (var product in data.products) {
|
||||
if (chart.get(product) === null) {
|
||||
chart.addSeries({
|
||||
data: [],
|
||||
id: product,
|
||||
name: product
|
||||
});
|
||||
}
|
||||
|
||||
var series = chart.get(product);
|
||||
_.each(data.products[product], function(point) {
|
||||
if (chart.get(point[0]) === null) {
|
||||
series.addPoint({
|
||||
id: point[0],
|
||||
x: point[0],
|
||||
y: point[1]
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function get_products_realtime_data() {
|
||||
var that = this;
|
||||
$.ajax( {
|
||||
url: products_realtime_url,
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
parse_products_realtime_data(data, products_realtime_chart);
|
||||
|
||||
setTimeout(function () {
|
||||
get_products_realtime_data();
|
||||
}, 15000);
|
||||
},
|
||||
cache: false
|
||||
});
|
||||
}
|
||||
|
||||
function renderProductCharts(products) {
|
||||
for(var i in products) {
|
||||
var render_to = 'product_chart' + products[i].id;
|
||||
var series_data = products[i].counts;
|
||||
series_data = _.map(series_data, function (el) { return [el.full_name, el.amount_total]; });
|
||||
|
||||
new Highcharts.Chart({
|
||||
chart: {
|
||||
renderTo: render_to,
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
plotShadow: false
|
||||
},
|
||||
title: {
|
||||
text: products[i].name
|
||||
},
|
||||
tooltip: {
|
||||
formatter: productFormatter
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
color: '#000000',
|
||||
connectorColor: '#000000',
|
||||
formatter: function() {
|
||||
return '<b>'+ this.point.name +'</b>: ' + this.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'pie',
|
||||
name: 'Orders',
|
||||
data: series_data
|
||||
}]
|
||||
});
|
||||
}
|
||||
}
|
||||
function renderOrdersChart(data) {
|
||||
var render_to = 'order_chart';
|
||||
|
||||
new Highcharts.Chart({
|
||||
chart: {
|
||||
renderTo: render_to,
|
||||
plotBackgroundColor: null,
|
||||
plotBorderWidth: null,
|
||||
plotShadow: false
|
||||
},
|
||||
title: {
|
||||
text: 'Fordeling av ordre på person'
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function () {
|
||||
return '<b>' + this.point.name + '</b>: ' + Highcharts.numberFormat(this.percentage, 1) + ' %';
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
allowPointSelect: true,
|
||||
cursor: 'pointer',
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
color: '#000000',
|
||||
connectorColor: '#000000',
|
||||
formatter: function () {
|
||||
return '<b>' + this.point.name + '</b>: ' + this.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'pie',
|
||||
name: 'Orders',
|
||||
data: data
|
||||
}]
|
||||
});
|
||||
}
|
||||
function renderOrdersHourlyChart(data) {
|
||||
var render_to = 'order_hourly_chart';
|
||||
|
||||
new Highcharts.Chart({
|
||||
chart: {
|
||||
renderTo: render_to,
|
||||
type: 'column'
|
||||
},
|
||||
title: {
|
||||
text: 'Ordre - time for time'
|
||||
},
|
||||
tooltip: {
|
||||
formatter: function() {
|
||||
return this.y + " stk";
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: 'Ordre'
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
title: {
|
||||
text: 'Tid (h)'
|
||||
},
|
||||
labels: {
|
||||
formatter: function() {
|
||||
return this.value + ":00";
|
||||
}
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
column: {
|
||||
minPointLength: 3
|
||||
},
|
||||
series: {
|
||||
groupPadding: 0,
|
||||
pointPadding: 0.05
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: 'Ordre',
|
||||
data: data.hourly
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
if( $("#stats").length > 0) {
|
||||
/* TODO select data element with index 0 of each pie-chart */
|
||||
|
||||
/* Products */
|
||||
$.getJSON(products_stat_url, function(data) {
|
||||
products = data;
|
||||
renderProductCharts(data);
|
||||
});
|
||||
/* Orders */
|
||||
$.getJSON(order_url, renderOrdersChart);
|
||||
|
||||
/* Orders, hourly */
|
||||
$.getJSON(order_hourly_url, renderOrdersHourlyChart);
|
||||
|
||||
/* Realtime purchases */
|
||||
products_realtime_chart = new Highcharts.Chart({
|
||||
chart: {
|
||||
renderTo: 'products_realtime_chart'
|
||||
},
|
||||
title: {
|
||||
text: "Produkter solgt over tid"
|
||||
},
|
||||
xAxis: {
|
||||
type: "datetime"
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: "Antall solgt"
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
series: {
|
||||
marker: {
|
||||
radius: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
series: {}
|
||||
});
|
||||
|
||||
get_products_realtime_data();
|
||||
}
|
||||
});
|
||||
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 852 B After Width: | Height: | Size: 923 B |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 725 B After Width: | Height: | Size: 977 B |
@@ -1 +1,3 @@
|
||||
function enable_all_customers(){$("li.customer").each(function(t){$(this).removeClass("marked"),$(this).show()})}function filter_customers(t){setTimeout(function(){$("li.customer").each(function(e){var r=new RegExp(t,"i"),i=$(this).children("span.name").text();if(r.test(i)){$(this).show();var a=i.toLowerCase()==t.toLowerCase(),o=1==$("li.customer:visible").length;console.log(a,o),a?$(this).addClass("marked"):$(this).removeClass("marked")}else $(this).hide(),$(this).removeClass("marked")})},50)}function enable_all_products(){$("li.product").each(function(t){$(this).removeClass("marked"),$(this).show()})}function reset_qtys(){$(".qty").each(function(t){$(this).html("0")}),$("input[name$=amount]").each(function(t){$(this).val("0")})}function update_total(){var t=0;$("li.product").each(function(){var e=Number($(this).find("[name$=amount]").val()),r=".sale_price_int";"Ekstern"===$("#id_customer_typeahead").val()&&(r=".sale_price_ext");var i=Number($(this).find(r).text());t+=e*i}),0===t&&(t=""),$("span.total_text").html(t)}$(document).ready(function(){function t(t){var e=n;for(var r in t.products)if(null===e.get(r)){e.addSeries({data:[],id:r,name:r});var i=e.get(r);_.each(t.products[r],function(t){i.addPoint({id:t[0],x:t[0],y:t[1]})})}else{var i=e.get(r);_.each(t.products[r],function(t){null===e.get(t[0])&&i.addPoint({id:t[0],x:t[0],y:t[1]})})}}function e(){$.ajax({url:s,dataType:"json",success:function(r){t(r),setTimeout(function(){e()},15e3)},cache:!1})}if($("a[data-dismiss=alert]").click(function(){$(this).parent().parent().parent().slideUp()}),setTimeout(function(){$("a[data-dismiss=alert]").parent().parent().parent().slideUp()},5e3),$("h2").tooltip({placement:"right"}),$("img.customer").tooltip(),$("a.product").tooltip(),$("#footer .credits .love").tooltip(),$("#id_customer_typeahead").keyup(function(t){filter_customers(t.target.value)}),$("li.customer").click(function(){enable_all_customers(),$(this).addClass("marked");var t=$(this).children("span.name").text();$("#id_customer_typeahead").val(t);var e=$(this).children("span.id").text();$("#id_customer option, #id_user option").removeAttr("selected"),""===e?$("#id_customer option:first-child, #id_user option:first-child").attr("selected","selected"):$("#id_customer option[value="+e+"], #id_user option[value="+e+"]").attr("selected","selected"),update_total(this)}),Highcharts.setOptions({credits:{enabled:!1}}),$("#register li.product").click(function(){$(this).addClass("marked");var t=$(this).children("input[name$=amount]"),e=$(this).children(".qty"),r=Number(t.val())+1;t.val(r),e.html(r),update_total(this)}),$("#register button[type=reset]").click(function(){$("#id_customer option").removeAttr("selected"),$("#id_customer_typeahead").val(""),$("span.total_text").html(""),enable_all_customers(),enable_all_products(),reset_qtys()}),$(".toggle-infrequent-users").on("click",function(t){t.preventDefault(),$("li.customer.infrequent").toggleClass("hidden"),$(".users-description .infrequent-users").toggleClass("hidden"),"Vis alle"===$(this).text()?$(this).text("Skjul gamliser"):$(this).text("Vis alle")}),$("#stats").length>0){var r=[],i="/stats/products/",a="/stats/orders/",o="/stats/orders/hourly",s="/stats/orders/products_realtime";$.getJSON(a,function(t){var e="order_chart",i={chart:{renderTo:e,plotBackgroundColor:null,plotBorderWidth:null,plotShadow:!1},title:{text:"Fordeling av ordre på person"},tooltip:{formatter:function(){return"<b>"+this.point.name+"</b>: "+Highcharts.numberFormat(this.percentage,1)+" %"}},plotOptions:{pie:{allowPointSelect:!0,cursor:"pointer",dataLabels:{enabled:!0,color:"#000000",connectorColor:"#000000",formatter:function(){return"<b>"+this.point.name+"</b>: "+this.y}}}},series:[{type:"pie",name:"Orders",data:t}]};r.push(new Highcharts.Chart(i))}),$.getJSON(i,function(t){var e=t.response.products;for(var i in e){var a="product_chart"+e[i].product.id,o=e[i].counts,s={chart:{renderTo:a,plotBackgroundColor:null,plotBorderWidth:null,plotShadow:!1},title:{text:e[i].product.name},tooltip:{formatter:function(){return"<b>"+this.point.name+"</b>: "+Highcharts.numberFormat(this.percentage,1)+" %"}},plotOptions:{pie:{allowPointSelect:!0,cursor:"pointer",dataLabels:{enabled:!0,color:"#000000",connectorColor:"#000000",formatter:function(){return"<b>"+this.point.name+"</b>: "+this.y}}}},series:[{type:"pie",name:"Orders",data:o}]};r.push(new Highcharts.Chart(s))}}),$.getJSON(o,function(t){var e="order_hourly_chart",i={chart:{renderTo:e,type:"column"},title:{text:"Ordre - time for time"},tooltip:{formatter:function(){return this.y+" stk"}},yAxis:{title:{text:"Ordre"}},xAxis:{title:{text:"Tid (h)"},labels:{formatter:function(){return this.value+":00"}}},plotOptions:{column:{minPointLength:3},series:{groupPadding:0,pointPadding:.05}},series:[{name:"Ordre",data:t.hourly}]};r.push(new Highcharts.Chart(i))});var n=new Highcharts.Chart({chart:{renderTo:"products_realtime_chart"},title:{text:"Produkter solgt over tid"},xAxis:{type:"datetime"},yAxis:{title:{teLine truncated
|
||||
function enable_all_customers(){$("li.customer").each(function(t){$(this).removeClass("marked"),$(this).show()})}function filter_customers(t){setTimeout(function(){$("li.customer").each(function(e){var i=new RegExp(t,"i"),a=$(this).children("span.name").text();if(i.test(a)){$(this).show();var s=a.toLowerCase()==t.toLowerCase(),n=1==$("li.customer:visible").length;console.log(s,n),s?$(this).addClass("marked"):$(this).removeClass("marked")}else $(this).hide(),$(this).removeClass("marked")})},50)}function enable_all_products(){$("li.product").each(function(t){$(this).removeClass("marked"),$(this).show()})}function reset_qtys(){$(".qty").each(function(t){$(this).html("0")}),$("input[name$=amount]").each(function(t){$(this).val("0")})}function update_total(){var t=0;$("li.product").each(function(){var e=Number($(this).find("[name$=amount]").val()),i=".sale_price_int";"Ekstern"===$("#id_customer_typeahead").val()&&(i=".sale_price_ext");var a=Number($(this).find(i).text());t+=e*a}),0===t&&(t=""),$("span.total_text").html(t)}$(document).ready(function(){var t=$("a[data-dismiss=alert]");t.click(function(){$(this).parent().parent().parent().slideUp()}),setTimeout(function(){t.parent().parent().parent().slideUp()},5e3),$("h2").tooltip({placement:"right"}),$("img.customer").tooltip(),$("a.product").tooltip(),$(".love").tooltip(),$("#id_customer_typeahead").keyup(function(t){filter_customers(t.target.value)}),$("li.customer").click(function(){enable_all_customers(),$(this).addClass("marked");var t=$(this).children("span.name").text();$("#id_customer_typeahead").val(t);var e=$(this).children("span.id").text();$("#id_customer option, #id_user option").removeAttr("selected"),""===e?$("#id_customer option:first-child, #id_user option:first-child").attr("selected","selected"):$("#id_customer option[value="+e+"], #id_user option[value="+e+"]").attr("selected","selected"),update_total(this)}),Highcharts.setOptions({credits:{enabled:!1}}),$("#register li.product").click(function(){$(this).addClass("marked");var t=$(this).children("input[name$=amount]"),e=$(this).children(".qty"),i=Number(t.val())+1;t.val(i),e.html(i),update_total(this)}),$("#register button[type=reset]").click(function(){$("#id_customer option").removeAttr("selected"),$("#id_customer_typeahead").val(""),$("span.total_text").html(""),enable_all_customers(),enable_all_products(),reset_qtys()}),$(".toggle-infrequent-users").on("click",function(t){t.preventDefault(),$("li.customer.infrequent").toggleClass("hidden"),$(".users-description .infrequent-users").toggleClass("hidden"),"Vis alle"===$(this).text()?$(this).text("Skjul gamliser"):$(this).text("Vis alle")});var e=$(".inventory-report-form #id_start_time");e.datepicker({language:"nb",format:"yyyy-mm-dd",todayHighlight:!0,weekStart:1,endDate:new Date}).on("changeDate",function(t){i.datepicker("setStartDate",t.date)});var i=$(".inventory-report-form #id_end_time");i.datepicker({language:"nb",format:"yyyy-mm-dd",todayHighlight:!0,weekStart:1,endDate:new Date}).on("changeDate",function(t){e.datepicker("setEndDate",t.date)})});
|
||||
$(document).ready(function(){var t=$("#user-profile");if(t.length){var e="/stats/products/by_user/",r=$("#user-profile").attr("data-user-id");$.getJSON(e+r,function(t){t.hasOwnProperty("error")||$(".profile-stats-orderline").highcharts({title:{text:!1},xAxis:{type:"datetime"},yAxis:{title:{text:"Kjøp"}},plotOptions:{series:{marker:{radius:3}}},series:t})})}});
|
||||
function productFormatter(){return"<b>"+this.point.name+"</b>: "+Highcharts.numberFormat(this.percentage,1)+" %"}function parse_products_realtime_data(t,r){for(var e in t.products){null===r.get(e)&&r.addSeries({data:[],id:e,name:e});var o=r.get(e);_.each(t.products[e],function(t){null===r.get(t[0])&&o.addPoint({id:t[0],x:t[0],y:t[1]})})}}function get_products_realtime_data(){$.ajax({url:products_realtime_url,dataType:"json",success:function(t){parse_products_realtime_data(t,products_realtime_chart),setTimeout(function(){get_products_realtime_data()},15e3)},cache:!1})}function renderProductCharts(t){for(var r in t){var e="product_chart"+t[r].id,o=t[r].counts;o=_.map(o,function(t){return[t.full_name,t.amount_total]}),new Highcharts.Chart({chart:{renderTo:e,plotBackgroundColor:null,plotBorderWidth:null,plotShadow:!1},title:{text:t[r].name},tooltip:{formatter:productFormatter},plotOptions:{pie:{allowPointSelect:!0,cursor:"pointer",dataLabels:{enabled:!0,color:"#000000",connectorColor:"#000000",formatter:function(){return"<b>"+this.point.name+"</b>: "+this.y}}}},series:[{type:"pie",name:"Orders",data:o}]})}}function renderOrdersChart(t){var r="order_chart";new Highcharts.Chart({chart:{renderTo:r,plotBackgroundColor:null,plotBorderWidth:null,plotShadow:!1},title:{text:"Fordeling av ordre på person"},tooltip:{formatter:function(){return"<b>"+this.point.name+"</b>: "+Highcharts.numberFormat(this.percentage,1)+" %"}},plotOptions:{pie:{allowPointSelect:!0,cursor:"pointer",dataLabels:{enabled:!0,color:"#000000",connectorColor:"#000000",formatter:function(){return"<b>"+this.point.name+"</b>: "+this.y}}}},series:[{type:"pie",name:"Orders",data:t}]})}function renderOrdersHourlyChart(t){var r="order_hourly_chart";new Highcharts.Chart({chart:{renderTo:r,type:"column"},title:{text:"Ordre - time for time"},tooltip:{formatter:function(){return this.y+" stk"}},yAxis:{title:{text:"Ordre"}},xAxis:{title:{text:"Tid (h)"},labels:{formatter:function(){return this.value+":00"}}},plotOptions:{column:{minPointLength:3},series:{groupPadding:0,pointPadding:.05}},series:[{name:"Ordre",data:t.hourly}]})}var products=null,products_realtime_chart=null,products_url="/api/products/",products_stat_url="/api/products/group_by_user",order_url="/stats/orders/",order_hourly_url="/stats/orders/hourly",products_realtime_url="/stats/orders/products_realtime";$(document).ready(function(){$("#stats").length>0&&($.getJSON(products_stat_url,function(t){products=t,renderProductCharts(t)}),$.getJSON(order_url,renderOrdersChart),$.getJSON(order_hourly_url,renderOrdersHourlyChart),products_realtime_chart=new Highcharts.Chart({chart:{renderTo:"products_realtime_chart"},title:{text:"Produkter solgt over tid"},xAxis:{type:"datetime"},yAxis:{title:{text:"Antall solgt"}},plotOptions:{series:{marker:{radius:3}}},series:{}}),get_products_realtime_data())});
|
||||
@@ -1 +1 @@
|
||||
/*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}hr{box-sizing:content-box;height:0}pre{overflow:auto}samp{font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url("/static/dist/fonts/glyphicons-halflings-regular.eot");src:url("/static/dist/fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("/static/dist/fonts/glyphicons-halflings-regular.woff") format("woff"),url("/static/dist/fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("/static/dist/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{Line truncated
|
||||
@charset "UTF-8";html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}hr{box-sizing:content-box;height:0}pre{overflow:auto}samp{font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url("/static/dist/fonts/glyphicons-halflings-regular.eot");src:url("/static/dist/fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("/static/dist/fonts/glyphicons-halflings-regular.woff") format("woff"),url("/static/dist/fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("/static/dist/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular") format("svg")}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{contLine truncated
|
||||
@@ -1,36 +1,33 @@
|
||||
'use strict';
|
||||
var gulp = require('gulp');
|
||||
var glob = require('glob');
|
||||
|
||||
// load plugins
|
||||
var $ = require('gulp-load-plugins')();
|
||||
var mainBowerFiles = require('main-bower-files'); // TODO: use these
|
||||
var bowerFiles = require('main-bower-files');
|
||||
var del = require('del');
|
||||
var browserSync = require('browser-sync').create();
|
||||
var reload = browserSync.reload;
|
||||
|
||||
gulp.task('styles', function () {
|
||||
return gulp.src('app/styles/*.scss')
|
||||
.pipe($.plumber())
|
||||
.pipe($.sass({
|
||||
errLogToConsole: true
|
||||
}))
|
||||
sourceComments: 'map'
|
||||
}).on('error', $.sass.logError))
|
||||
.pipe($.autoprefixer('last 1 version'))
|
||||
.pipe(gulp.dest('dist/styles'))
|
||||
.pipe($.size());
|
||||
});
|
||||
|
||||
gulp.task('scripts', function () {
|
||||
return gulp.src('app/js/**/*.js')
|
||||
.pipe($.jshint())
|
||||
.pipe($.jshint.reporter(require('jshint-stylish')))
|
||||
.pipe($.concat('app.js'))
|
||||
.pipe(gulp.dest('dist/js'))
|
||||
.pipe($.size());
|
||||
});
|
||||
|
||||
gulp.task('diststyles', function () {
|
||||
return gulp.src('app/styles/*.scss')
|
||||
.pipe($.plumber())
|
||||
.pipe($.sass({
|
||||
errLogToConsole: true
|
||||
}))
|
||||
.pipe($.sass()
|
||||
.on('error', $.sass.logError))
|
||||
// TODO: do this with a custom django-compressor filter, too much maintenance otherwise
|
||||
// .pipe($.uncss({
|
||||
// html: glob.sync('../templates/**/*.html'),
|
||||
@@ -39,7 +36,6 @@ gulp.task('diststyles', function () {
|
||||
.pipe($.autoprefixer('last 1 version'))
|
||||
.pipe($.csso())
|
||||
.pipe(gulp.dest('dist/styles'))
|
||||
.pipe($.size());
|
||||
});
|
||||
|
||||
gulp.task('distscripts', function () {
|
||||
@@ -47,8 +43,8 @@ gulp.task('distscripts', function () {
|
||||
.pipe($.jshint())
|
||||
.pipe($.jshint.reporter(require('jshint-stylish')))
|
||||
.pipe($.uglify())
|
||||
.pipe($.concat('app.js'))
|
||||
.pipe(gulp.dest('dist/js'))
|
||||
.pipe($.size());
|
||||
});
|
||||
|
||||
gulp.task('images', function () {
|
||||
@@ -59,43 +55,44 @@ gulp.task('images', function () {
|
||||
interlaced: true
|
||||
}))
|
||||
.pipe(gulp.dest('dist/images'))
|
||||
.pipe($.size());
|
||||
});
|
||||
|
||||
gulp.task('fonts', function () {
|
||||
return gulp.src(mainBowerFiles())
|
||||
.pipe($.filter('**/*.{eot,svg,ttf,woff}'))
|
||||
return gulp.src(bowerFiles())
|
||||
.pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}'))
|
||||
.pipe($.flatten())
|
||||
.pipe(gulp.dest('dist/fonts'))
|
||||
.pipe($.size());
|
||||
});
|
||||
|
||||
gulp.task('clean', function () {
|
||||
return gulp.src(['dist'], { read: false }).pipe($.clean());
|
||||
gulp.task('clean', function (cb) {
|
||||
return del('dist', cb);
|
||||
});
|
||||
|
||||
gulp.task('build', ['distscripts', 'diststyles', 'images', 'fonts']);
|
||||
|
||||
gulp.task(
|
||||
'build',
|
||||
['distscripts', 'diststyles', 'images', 'fonts'],
|
||||
function() {
|
||||
return gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
|
||||
}
|
||||
);
|
||||
gulp.task('default', ['clean'], function () {
|
||||
gulp.start('build');
|
||||
});
|
||||
|
||||
gulp.task('serve', ['styles'], function () {
|
||||
require('opn')('http://localhost:8000');
|
||||
});
|
||||
|
||||
gulp.task('watch', ['serve'], function () {
|
||||
var server = $.livereload();
|
||||
browserSync.init({
|
||||
proxy: '127.0.0.1:8000'
|
||||
});
|
||||
|
||||
// watch for changes
|
||||
|
||||
gulp.watch([
|
||||
'../templates/**/*.html',
|
||||
'app/js/**/*.js',
|
||||
'app/images/**/*'
|
||||
]).on('change', function (file) {
|
||||
server.changed(file.path);
|
||||
});
|
||||
]).on('change', reload);
|
||||
|
||||
gulp.watch('app/styles/**/*.scss', ['styles']);
|
||||
gulp.watch('app/js/**/*.js', ['scripts']);
|
||||
|
||||
@@ -4,27 +4,26 @@
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"glob": "^4.0.6",
|
||||
"gulp": "^3.8.8",
|
||||
"gulp-autoprefixer": "0.0.7",
|
||||
"gulp-bower-files": "^0.2.1",
|
||||
"browser-sync": "^2.10.0",
|
||||
"del": "^2.1.0",
|
||||
"glob": "^6.0.1",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-autoprefixer": "3.1.0",
|
||||
"gulp-cache": "^0.1.11",
|
||||
"gulp-clean": "^0.2.4",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-csso": "^0.2.6",
|
||||
"gulp-filter": "^0.4.1",
|
||||
"gulp-filter": "^3.0.1",
|
||||
"gulp-flatten": "0.0.2",
|
||||
"gulp-imagemin": "^0.5.1",
|
||||
"gulp-jshint": "^1.5.3",
|
||||
"gulp-livereload": "^1.5.0",
|
||||
"gulp-load-plugins": "^0.5.3",
|
||||
"gulp-plumber": "^0.6.5",
|
||||
"gulp-sass": "^2.0.4",
|
||||
"gulp-size": "^0.3.1",
|
||||
"gulp-uglify": "^0.2.1",
|
||||
"gulp-uncss": "^0.4.5",
|
||||
"jshint-stylish": "^0.2.0",
|
||||
"main-bower-files": "^2.0.0",
|
||||
"opn": "^0.1.2"
|
||||
"gulp-if": "^2.0.0",
|
||||
"gulp-imagemin": "^2.4.0",
|
||||
"gulp-jshint": "^2.0.0",
|
||||
"gulp-load-plugins": "^1.1.0",
|
||||
"gulp-sass": "^2.1.0",
|
||||
"gulp-size": "^2.0.0",
|
||||
"gulp-uglify": "^1.5.1",
|
||||
"gulp-uncss": "^1.0.4",
|
||||
"jshint-stylish": "^2.1.0",
|
||||
"main-bower-files": "^2.9.0"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -72,29 +72,30 @@
|
||||
<h2>Kjøper hva?</h2>
|
||||
<ul class="well">
|
||||
{% for product in products %}
|
||||
<li id="form-{{forloop.counter0}}" class="product"><input type="hidden" name="form-{{ forloop.counter0 }}-product" value="{{product.id }}" />
|
||||
<input type="hidden" id="form-{{ forloop.counter0 }}-amount" name="form-{{ forloop.counter0 }}-amount" value="0" class="span2" size="2" />
|
||||
<input type="hidden" name="form-{{ forloop.counter0 }}-unit_price_int" value="{{product.sale_price_int }}" />
|
||||
<input type="hidden" name="form-{{ forloop.counter0 }}-unit_price_ext" value="{{product.sale_price_ext }}" />
|
||||
<span class="qty">0</span>
|
||||
{% if product.image %}
|
||||
<img class="product" src="{{ product.image.url }}" /><br />
|
||||
{% else %}
|
||||
<img class="product" src="{% static "dist/images/product.png" %}" /><br />
|
||||
{% endif %}
|
||||
<span class="name">{{ product.name }}</span> - <span class="sale_price_ext">{{product.sale_price_ext|floatformat:0}}</span>/<span class="sale_price_int">{{product.sale_price_int|floatformat:0}}</span>
|
||||
</li>
|
||||
<li id="form-{{forloop.counter0}}" class="product"><input type="hidden" name="form-{{ forloop.counter0 }}-product" value="{{product.id }}" />
|
||||
<input type="hidden" id="form-{{ forloop.counter0 }}-amount" name="form-{{ forloop.counter0 }}-amount" value="0" class="span2" size="2" />
|
||||
<input type="hidden" name="form-{{ forloop.counter0 }}-unit_price_int" value="{{product.sale_price_int }}" />
|
||||
<input type="hidden" name="form-{{ forloop.counter0 }}-unit_price_ext" value="{{product.sale_price_ext }}" />
|
||||
<span class="qty">0</span>
|
||||
{% if product.image %}
|
||||
<img class="product" src="{{ product.image.url }}" /><br />
|
||||
{% else %}
|
||||
<img class="product" src="{% static "dist/images/product.png" %}" /><br />
|
||||
{% endif %}
|
||||
<span class="name">{{ product.name }}</span><br>
|
||||
<span class="sale_price_ext">{{product.sale_price_ext|floatformat:0}}</span>/<span class="sale_price_int">{{product.sale_price_int|floatformat:0}}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
</fieldset>
|
||||
<div class="form-actions">
|
||||
<div class="col-xs-3 total">
|
||||
<h2>Totalt: <span class="total_text"></span></h2>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-lg btn-primary" name="submit_order">Kjøp</button>
|
||||
<button type="reset" class="btn btn-default">Start på nytt</button>
|
||||
<div class="col-xs-3 total">
|
||||
<h2>Totalt: <span class="total_text"></span></h2>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-lg btn-primary" name="submit_order">Kjøp</button>
|
||||
<button type="reset" class="btn btn-default">Start på nytt</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||