UI improvements and logic for selected user
* Starts work on simple flow for updating member card for existing members
This commit is contained in:
1 parent
2eb9ba5f30
commit
60d1186d0d
9 files changed
+552
-154
No files matched your search
@@ -7,18 +7,29 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<form>
|
||||
<div class="col-sm-5">
|
||||
<form class="register-card-form" autocomplete="off">
|
||||
{% csrf_token %}
|
||||
<h3>{% trans "Register card" %}</h3>
|
||||
<span class="glyphicon glyphicon-phone"></span>
|
||||
<div class="register-card-form--selected-user-wrap">
|
||||
<!-- Placeholder -->
|
||||
<div class="list-group-item search-result placeholder">
|
||||
<input type="radio" name="user" checked>
|
||||
<em>New user</em>
|
||||
<div class="labels">
|
||||
<span class="label label-card "><span class="glyphicon glyphicon-user"></span> New card</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ add_card_form|bootstrap }}
|
||||
<p><em>Registering a card number with a phone number already tied to an existing user, will add the card to that user.</em></p>
|
||||
<button id="register-submit-btn" type="submit" class="btn btn-primary btn-lg">{% trans "Register" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="col-sm-7">
|
||||
<h3>{% trans "Search existing users" %}</h3>
|
||||
<form class="form-inline query-form">
|
||||
<em>{% trans "Firstname, lastname, card number, email" %}</em>
|
||||
<form class="form-inline query-form" autocomplete="off">
|
||||
{{ search_user_form|bootstrap_inline }}
|
||||
</form>
|
||||
<div class="results-wrap"></div>
|
||||
|
||||
+5
-2
@@ -1,9 +1,12 @@
|
||||
from django.conf.urls import url
|
||||
from apps.kassa.views import register, inside_user_api, inside_phone_number_api, inside_card_number_api
|
||||
from apps.kassa.views import register, inside_user_api, inside_phone_number_api, inside_card_number_api, \
|
||||
inside_register_api
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', register, name='register'),
|
||||
url(r'^inside/user/$', inside_user_api, name='inside-user-api'),
|
||||
url(r'^inside/phonenumber/$', inside_phone_number_api, name='inside-phone-number-api'),
|
||||
url(r'^inside/cardnumber/$', inside_card_number_api, name='inside-card-number-api')
|
||||
url(r'^inside/cardnumber/$', inside_card_number_api, name='inside-card-number-api'),
|
||||
url(r'^inside/register/$', inside_register_api, name='inside-register-api')
|
||||
|
||||
]
|
||||
+41
-5
@@ -1,12 +1,14 @@
|
||||
# coding: utf-8
|
||||
from __future__ import unicode_literals
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render
|
||||
import requests
|
||||
|
||||
from apps.kassa.forms import AddCardForm, SearchUserForm
|
||||
import requests
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -44,11 +46,45 @@ def inside_phone_number_api(request):
|
||||
|
||||
@login_required
|
||||
def inside_card_number_api(request):
|
||||
url = '{}cardnumber.php'.format(settings.INSIDE_API_URL)
|
||||
params = {
|
||||
'apikey': settings.INSIDE_API_KEY
|
||||
}
|
||||
|
||||
if request.method == 'POST':
|
||||
post_data = json.loads(request.body)
|
||||
payload = {
|
||||
'card_number': post_data.get('card_number'),
|
||||
'user_id': post_data.get('user_id')
|
||||
}
|
||||
print payload
|
||||
response = requests.post(
|
||||
url,
|
||||
data=json.dumps(payload),
|
||||
params=params,
|
||||
headers=dict(content_type='application/json')
|
||||
)
|
||||
else:
|
||||
params.update({
|
||||
'card_number': request.GET.get('card_number', '')
|
||||
})
|
||||
response = requests.get(url, params=params).json()
|
||||
|
||||
return JsonResponse(response.json(), status=response.status_code)
|
||||
|
||||
|
||||
@login_required
|
||||
def inside_register_api(request):
|
||||
post_data = json.loads(request.body)
|
||||
payload = {
|
||||
'apikey': settings.INSIDE_API_KEY,
|
||||
'q': request.GET.get('q', '')
|
||||
'phone': post_data.get('phone_number'),
|
||||
# 'user_id': post_data.get('user_id'),
|
||||
'type': post_data.get('type'), # 'renewal' or 'new'
|
||||
'cardno': post_data.get('cardno'),
|
||||
'source': 'physical'
|
||||
}
|
||||
url = '{}cardnumber.php'.format(settings.INSIDE_API_URL)
|
||||
data = requests.get(url, params=payload).json()
|
||||
url = '{}register.php'.format(settings.INSIDE_API_URL)
|
||||
response = requests.post(url, data=json.dumps(payload), headers=dict(content_type='application/json'))
|
||||
|
||||
return JsonResponse(data)
|
||||
return JsonResponse(response.json(), status=response.status_code)
|
||||
+189
-45
@@ -1,7 +1,50 @@
|
||||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function getParameterByName(name) {
|
||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||
results = regex.exec(location.search);
|
||||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||
}
|
||||
function format_results(results) {
|
||||
return nunjucks.render('search_results.html', {'results':results});
|
||||
}
|
||||
function render_selected_user(user_data) {
|
||||
user_data.checked = true;
|
||||
var selected_user_html = nunjucks.render('search_result.html', user_data);
|
||||
_dom.selectedUserWrap.html(selected_user_html);
|
||||
}
|
||||
function set_selected_user(user, update_search_result) {
|
||||
selectedUser = user;
|
||||
var new_val = '';
|
||||
if(user) {
|
||||
new_val = user.id;
|
||||
}
|
||||
_dom.userIdField.val(new_val).trigger('change');
|
||||
if(update_search_result) {
|
||||
var stale_search_result = _dom.results.find('.search-result').removeClass('selected');
|
||||
stale_search_result.find('input').prop('checked', false);
|
||||
/* User is still in search result, mark selected */
|
||||
if( user && _.findWhere(users, {id:user.id}) ) {
|
||||
var search_result = _dom.results.find('[data-user-id="'+ user.id +'"]').toggleClass('selected');
|
||||
search_result.find('input').prop('checked', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function set_field_state(field_element, state, help_text) {
|
||||
if(!help_text) {
|
||||
@@ -43,7 +86,15 @@ function set_field_state(field_element, state, help_text) {
|
||||
}
|
||||
}
|
||||
|
||||
function validate_cardnumber(val) {
|
||||
function validatePhoneNumber(val) {
|
||||
if(val.length === 0) {
|
||||
return 'Phonenumber number should not be empty.';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function validateCardNumber(val) {
|
||||
if(val.length === 0) {
|
||||
return 'Card number should not be empty.';
|
||||
}
|
||||
@@ -57,20 +108,34 @@ function validate_cardnumber(val) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function validate_form() {
|
||||
function getFormData(formElement) {
|
||||
var formData = formElement.serializeArray();
|
||||
formData = _.object(_.map(formData, function (x) {
|
||||
return [x.name, x.value];
|
||||
}));
|
||||
|
||||
return formData;
|
||||
}
|
||||
function validateCardForm() {
|
||||
var formData = getFormData(_dom.registerCardForm);
|
||||
var errors = [];
|
||||
/* TODO
|
||||
- phonenumber should be valid (python-phonenumbers)
|
||||
- userid exists (optional)
|
||||
*/
|
||||
/* Check each fields validation state */
|
||||
/* Checkout multifield validation state */
|
||||
var cardno_val = _dom.cardNumber.val();
|
||||
var cardno_invalid = validate_cardnumber(cardno_val);
|
||||
/* Card number */
|
||||
var cardno_invalid = validateCardNumber(formData.card_number);
|
||||
if(cardno_invalid) {
|
||||
set_field_state(_dom.cardNumber, 'error', cardno_invalid);
|
||||
set_field_state(_dom.cardNumberField, 'error', cardno_invalid);
|
||||
errors.push(cardno_invalid);
|
||||
}
|
||||
/* Phone number */
|
||||
var phone_number_invalid = validatePhoneNumber(formData.phone_number);
|
||||
if(phone_number_invalid) {
|
||||
set_field_state(_dom.phoneNumberField, 'error', phone_number_invalid);
|
||||
errors.push(phone_number_invalid);
|
||||
}
|
||||
console.log("Errors: ", errors);
|
||||
|
||||
if(errors.length === 0) {
|
||||
_dom.registerButton.removeAttr('disabled');
|
||||
@@ -82,22 +147,29 @@ function validate_form() {
|
||||
|
||||
|
||||
var _dom;
|
||||
var register_form_fields = ['user_id', 'phone_number', 'card_number'];
|
||||
var users;
|
||||
var selectedUser;
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
_dom = {
|
||||
results: $('.results-wrap'),
|
||||
query: $('#id_query'),
|
||||
phoneNumber: $('#id_phone_number'),
|
||||
cardNumber: $('#id_card_number'),
|
||||
phoneNumberLabel: $('[for=id_phone_number]'),
|
||||
phoneNumberField: $('#id_phone_number'),
|
||||
cardNumberField: $('#id_card_number'),
|
||||
registerCardForm: $('.register-card-form'),
|
||||
registerButton: $('#register-submit-btn'),
|
||||
usernameField: $('#id_username'),
|
||||
userId: $('#id_user_id')
|
||||
userIdField: $('#id_user_id'),
|
||||
selectedUserWrap: $('.register-card-form--selected-user-wrap')
|
||||
};
|
||||
var urls = {
|
||||
insideUserApi: '/inside/user/',
|
||||
insidePhoneNumberApi: '/inside/phonenumber/',
|
||||
insideCardNumber: '/inside/cardnumber/'
|
||||
insideCardNumber: '/inside/cardnumber/',
|
||||
insideRegister: '/inside/register/'
|
||||
};
|
||||
|
||||
/* User search as you type */
|
||||
@@ -110,55 +182,61 @@ $(document).ready(function(){
|
||||
console.log(data);
|
||||
if(data.results && data.results.length > 0) {
|
||||
_dom.results.html(format_results(data.results));
|
||||
} else {
|
||||
users = data.results;
|
||||
}
|
||||
else if(data.error) {
|
||||
_dom.results.html(data.error);
|
||||
}
|
||||
else {
|
||||
if(val === "") {
|
||||
_dom.results.html('');
|
||||
} else {
|
||||
_dom.results.html('No existing user with that phone number');
|
||||
_dom.results.html("Found no existing user with search param: '"+ val +"'");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* Phone number as you type */
|
||||
_dom.phoneNumber.on('keyup', function() {
|
||||
var val = _dom.phoneNumber.val().trim();
|
||||
if(val.length === 0) {
|
||||
set_field_state(_dom.phoneNumber, '');
|
||||
return 'Phonenumber number should not be empty.';
|
||||
}
|
||||
$.getJSON(urls.insidePhoneNumberApi, {q: val}, function(data) {
|
||||
if(data.meta && data.meta.num_results == 1) {
|
||||
set_field_state(_dom.phoneNumber, 'success');
|
||||
_dom.userId.val(data.results[0].id);
|
||||
} else {
|
||||
set_field_state(_dom.phoneNumber, '');
|
||||
_dom.userId.val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
/* Card number as you type */
|
||||
_dom.cardNumber.on('keyup', function() {
|
||||
if(!validate_form('cardNumber')) {
|
||||
_dom.phoneNumberField.on('input', function() {
|
||||
if( !validateCardForm() ) {
|
||||
return;
|
||||
}
|
||||
var val = _dom.phoneNumberField.val().trim();
|
||||
$.getJSON(urls.insidePhoneNumberApi, {q: val}, function(data) {
|
||||
if(data.meta && data.meta.num_results == 1) {
|
||||
set_field_state(_dom.phoneNumberField, 'success');
|
||||
set_selected_user(data.results[0], true);
|
||||
} else {
|
||||
set_field_state(_dom.phoneNumberField, '');
|
||||
set_selected_user('', true);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
/* Card number as you type */
|
||||
_dom.cardNumberField.on('input', function() {
|
||||
/* Card number should exist in the database and not tied to existing user */
|
||||
var val = _dom.cardNumber.val().trim();
|
||||
$.getJSON(urls.insideCardNumber, {q: val}, function(data) {
|
||||
if( !validateCardForm() ) {
|
||||
return;
|
||||
}
|
||||
var val = _dom.cardNumberField.val().trim();
|
||||
$.getJSON(urls.insideCardNumber, {card_number: val}, function(data) {
|
||||
if(data.error) {
|
||||
set_field_state(_dom.cardNumber, 'error', data.error);
|
||||
set_field_state(_dom.cardNumberField, 'error', data.error);
|
||||
return;
|
||||
}
|
||||
if(!data.valid) {
|
||||
set_field_state(_dom.cardNumber, 'error', 'Cannot find card number in database.');
|
||||
set_field_state(_dom.cardNumberField, 'error', 'Cannot find card number in database.');
|
||||
}
|
||||
else if(data.user !== null && data.valid) {
|
||||
set_field_state(_dom.cardNumber, 'error', 'Card number belongs to existing user: '+ data.user.firstname +''+ data.user.lastname +'('+data.user.id+').');
|
||||
var user = data.user[0];
|
||||
set_field_state(_dom.cardNumberField, 'error', 'Card number belongs to existing user: '+ user.firstname +' '+ user.lastname +' ('+user.id+').');
|
||||
}
|
||||
else if(data.user === null && data.valid) {
|
||||
set_field_state(_dom.cardNumber, 'success');
|
||||
set_field_state(_dom.cardNumberField, 'success');
|
||||
} else {
|
||||
set_field_state(_dom.cardNumber, '');
|
||||
set_field_state(_dom.cardNumberField, '');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -168,23 +246,89 @@ $(document).ready(function(){
|
||||
var label = $(this).parent();
|
||||
var number = label.attr('data-phone-number');
|
||||
var user_id = label.attr('data-user-id');
|
||||
|
||||
/* Update selected state */
|
||||
$('.search-result').removeClass('selected');
|
||||
label.toggleClass('selected');
|
||||
_dom.userId.val(user_id);
|
||||
|
||||
set_selected_user(_.findWhere(users, {id: user_id}));
|
||||
|
||||
/* Update phone number field */
|
||||
if(number === '-') {
|
||||
number = '';
|
||||
}
|
||||
_dom.phoneNumber.val(number);
|
||||
_dom.phoneNumberField.val(number);
|
||||
});
|
||||
|
||||
/* Initial state disabled */
|
||||
_dom.registerButton.attr('disabled','disabled');
|
||||
_dom.registerButton.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
// TODO on error output validation status
|
||||
// TODO on success:
|
||||
// - print what action was performed and suggest steps
|
||||
// - show new user below form (hide/show after 10s)
|
||||
// - clear form
|
||||
|
||||
if( !validateCardForm() ) {
|
||||
// ERROR
|
||||
return;
|
||||
}
|
||||
var url = urls.insideRegister;
|
||||
var payload = getFormData(_dom.registerCardForm);
|
||||
|
||||
/* Just updating card number? */
|
||||
if(payload.user_id && payload.card_number && selectedUser.is_member === "1") {
|
||||
url = urls.insideCardNumber;
|
||||
}
|
||||
$.ajax(url, {
|
||||
data: JSON.stringify(payload),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
type: 'post',
|
||||
headers: {'X-CSRFToken': getCookie('csrftoken')}
|
||||
}).success(function(data){
|
||||
console.log("success", data);
|
||||
|
||||
}).fail(function(data) {
|
||||
console.log("failed", data);
|
||||
});
|
||||
});
|
||||
|
||||
/* On user id change */
|
||||
_dom.userIdField.on('change', function() {
|
||||
/* Render selected user in search form */
|
||||
console.log("try updating selected user ", selectedUser);
|
||||
if(selectedUser) {
|
||||
render_selected_user({res: selectedUser});
|
||||
return;
|
||||
}
|
||||
render_selected_user({placeholder: true});
|
||||
});
|
||||
|
||||
/* Ninja add some icons to bootstrap form */
|
||||
var inputGroup = '<div class="input-group">';
|
||||
var iconMobile = '<span class="input-group-addon"><span class="glyphicon glyphicon-phone"></span></span>';
|
||||
_dom.phoneNumberField.wrap(inputGroup);
|
||||
_dom.phoneNumberField.before(iconMobile);
|
||||
|
||||
/* Initial focus */
|
||||
if(_dom.usernameField) {
|
||||
_dom.usernameField.focus();
|
||||
}
|
||||
if(_dom.phoneNumber) {
|
||||
_dom.phoneNumber.focus();
|
||||
if(_dom.phoneNumberField) {
|
||||
_dom.phoneNumberField.focus();
|
||||
}
|
||||
/* Initial submit button state disabled */
|
||||
_dom.registerButton.attr('disabled','disabled');
|
||||
|
||||
/* Load query params */
|
||||
/* FIXME: does not trigger validation and selected user preview */
|
||||
if( getParameterByName('user_id') ) {
|
||||
_dom.userIdField.val(getParameterByName('user_id')).trigger('change');
|
||||
}
|
||||
if( getParameterByName('phone_number') ) {
|
||||
_dom.phoneNumberField.val(getParameterByName('phone_number'));
|
||||
}
|
||||
if( getParameterByName('card_number') ) {
|
||||
_dom.cardNumberField.val(getParameterByName('card_number'));
|
||||
}
|
||||
});
|
||||
@@ -6,6 +6,15 @@ $icon-font-path: '../fonts/';
|
||||
/* Global styles */
|
||||
body {
|
||||
}
|
||||
input[type=number]::-webkit-outer-spin-button,
|
||||
input[type=number]::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance:textfield;
|
||||
}
|
||||
|
||||
/* Helpers */
|
||||
.center-block {
|
||||
@@ -17,6 +26,30 @@ body {
|
||||
text-align: right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.register-card-form {
|
||||
|
||||
input[type=tel],
|
||||
input[type=number] {
|
||||
height: 46px;
|
||||
padding: 10px 16px;
|
||||
font-size: 18px;
|
||||
line-height: 1.3333333;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.form-control-feedback {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
line-height: 46px;
|
||||
}
|
||||
}
|
||||
.glyphicon-phone {
|
||||
font-size: 26px;
|
||||
}
|
||||
.register-card-form--selected-user-wrap {
|
||||
min-height: 78px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.query-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,46 @@
|
||||
{% if not res.cardno or res.card_is_legacy %}
|
||||
<label for="{{ res.id }}" data-phone-number="{{ res.number }}" data-user-id="{{ res.id }}" class="list-group-item search-result can-register-card{% if res.is_member == "1" %} member{% else %} not-member{% endif %}">
|
||||
<input type="radio" name="user" id="{{ res.id }}">
|
||||
{% else %}
|
||||
<!-- Has card -->
|
||||
<div class="list-group-item search-result has-card{% if res.is_member %} member{% else %}not-member{% endif %}">
|
||||
{% endif %}
|
||||
<!-- Name -->
|
||||
{{ res.firstname }} {{ res.lastname }}
|
||||
<div class="labels">
|
||||
<!-- Membership -->
|
||||
{% if res.is_member == "1" %}
|
||||
<span class="label label-success">Member: {{res.expires|default('Lifelong')}}</span>
|
||||
{% elif res.expires == "0000-00-00" %}
|
||||
<span class="label label-default">Registered</span>
|
||||
{% if not placeholder %}
|
||||
{% if not res.cardno or res.card_is_legacy %}
|
||||
<label for="{{ res.id }}" data-phone-number="{{ res.number }}" data-user-id="{{ res.id }}" class="list-group-item search-result can-register-card{% if res.is_member == "1" %} member{% else %} not-member{% endif %}">
|
||||
<input type="radio" name="user" id="{{ res.id }}"{% if checked %} checked{% endif %}>
|
||||
{% else %}
|
||||
<span class="label label-warning">Expired: {{ res.expires }}</span>
|
||||
<!-- Has card -->
|
||||
<div class="list-group-item search-result has-card{% if res.is_member %} member{% else %}not-member{% endif %}">
|
||||
{% endif %}
|
||||
<!-- Card -->
|
||||
{% if res.cardno and not res.card_is_legacy %}
|
||||
<span class="label label-card "><span class="glyphicon glyphicon-user"></span> {{ res.cardno }}</span>
|
||||
<!-- Name -->
|
||||
{{ res.firstname }} {{ res.lastname }}
|
||||
<div class="labels">
|
||||
<!-- Membership -->
|
||||
{% if res.is_member == "1" %}
|
||||
<span class="label label-success">Member: {{res.expires|default('Lifelong')}}</span>
|
||||
{% elif res.expires == "0000-00-00" %}
|
||||
<span class="label label-default">Registered</span>
|
||||
{% else %}
|
||||
<span class="label label-warning">Expired: {{ res.expires }}</span>
|
||||
{% endif %}
|
||||
<!-- Card -->
|
||||
{% if not res.card_is_legacy %}
|
||||
<span class="label label-card "><span class="glyphicon glyphicon-user"></span> {{ res.cardno|default('No card') }}</span>
|
||||
{% else %}
|
||||
<span class="label label-card "><span class="glyphicon glyphicon-user"></span> No card</span>
|
||||
{% endif %}
|
||||
<!-- Is active -->
|
||||
{% if res.is_active == "1" %}
|
||||
<span class="label label-is-active">Active</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not res.cardno or res.card_is_legacy %}
|
||||
<div class="selected-icon"><span class="glyphicon glyphicon-ok"></span></div>
|
||||
</label>
|
||||
{% else %}
|
||||
<span class="label label-card "><span class="glyphicon glyphicon-user"></span> No card</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!-- Is active -->
|
||||
{% if res.is_active == "1" %}
|
||||
<span class="label label-is-active">Active</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not res.cardno or res.card_is_legacy %}
|
||||
<div class="selected-icon"><span class="glyphicon glyphicon-ok"></span></div>
|
||||
</label>
|
||||
{% else %}
|
||||
<!-- Placeholder -->
|
||||
<div class="list-group-item search-result placeholder">
|
||||
<input type="radio" name="user" checked>
|
||||
<em>New</em>
|
||||
<div class="labels">
|
||||
<span class="label label-card "><span class="glyphicon glyphicon-user"></span> {{ res.cardno|default('New card') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
Vendored
+189
-45
@@ -1,7 +1,50 @@
|
||||
function getCookie(name) {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
function getParameterByName(name) {
|
||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||
results = regex.exec(location.search);
|
||||
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||
}
|
||||
function format_results(results) {
|
||||
return nunjucks.render('search_results.html', {'results':results});
|
||||
}
|
||||
function render_selected_user(user_data) {
|
||||
user_data.checked = true;
|
||||
var selected_user_html = nunjucks.render('search_result.html', user_data);
|
||||
_dom.selectedUserWrap.html(selected_user_html);
|
||||
}
|
||||
function set_selected_user(user, update_search_result) {
|
||||
selectedUser = user;
|
||||
var new_val = '';
|
||||
if(user) {
|
||||
new_val = user.id;
|
||||
}
|
||||
_dom.userIdField.val(new_val).trigger('change');
|
||||
if(update_search_result) {
|
||||
var stale_search_result = _dom.results.find('.search-result').removeClass('selected');
|
||||
stale_search_result.find('input').prop('checked', false);
|
||||
/* User is still in search result, mark selected */
|
||||
if( user && _.findWhere(users, {id:user.id}) ) {
|
||||
var search_result = _dom.results.find('[data-user-id="'+ user.id +'"]').toggleClass('selected');
|
||||
search_result.find('input').prop('checked', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function set_field_state(field_element, state, help_text) {
|
||||
if(!help_text) {
|
||||
@@ -43,7 +86,15 @@ function set_field_state(field_element, state, help_text) {
|
||||
}
|
||||
}
|
||||
|
||||
function validate_cardnumber(val) {
|
||||
function validatePhoneNumber(val) {
|
||||
if(val.length === 0) {
|
||||
return 'Phonenumber number should not be empty.';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function validateCardNumber(val) {
|
||||
if(val.length === 0) {
|
||||
return 'Card number should not be empty.';
|
||||
}
|
||||
@@ -57,20 +108,34 @@ function validate_cardnumber(val) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function validate_form() {
|
||||
function getFormData(formElement) {
|
||||
var formData = formElement.serializeArray();
|
||||
formData = _.object(_.map(formData, function (x) {
|
||||
return [x.name, x.value];
|
||||
}));
|
||||
|
||||
return formData;
|
||||
}
|
||||
function validateCardForm() {
|
||||
var formData = getFormData(_dom.registerCardForm);
|
||||
var errors = [];
|
||||
/* TODO
|
||||
- phonenumber should be valid (python-phonenumbers)
|
||||
- userid exists (optional)
|
||||
*/
|
||||
/* Check each fields validation state */
|
||||
/* Checkout multifield validation state */
|
||||
var cardno_val = _dom.cardNumber.val();
|
||||
var cardno_invalid = validate_cardnumber(cardno_val);
|
||||
/* Card number */
|
||||
var cardno_invalid = validateCardNumber(formData.card_number);
|
||||
if(cardno_invalid) {
|
||||
set_field_state(_dom.cardNumber, 'error', cardno_invalid);
|
||||
set_field_state(_dom.cardNumberField, 'error', cardno_invalid);
|
||||
errors.push(cardno_invalid);
|
||||
}
|
||||
/* Phone number */
|
||||
var phone_number_invalid = validatePhoneNumber(formData.phone_number);
|
||||
if(phone_number_invalid) {
|
||||
set_field_state(_dom.phoneNumberField, 'error', phone_number_invalid);
|
||||
errors.push(phone_number_invalid);
|
||||
}
|
||||
console.log("Errors: ", errors);
|
||||
|
||||
if(errors.length === 0) {
|
||||
_dom.registerButton.removeAttr('disabled');
|
||||
@@ -82,22 +147,29 @@ function validate_form() {
|
||||
|
||||
|
||||
var _dom;
|
||||
var register_form_fields = ['user_id', 'phone_number', 'card_number'];
|
||||
var users;
|
||||
var selectedUser;
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
_dom = {
|
||||
results: $('.results-wrap'),
|
||||
query: $('#id_query'),
|
||||
phoneNumber: $('#id_phone_number'),
|
||||
cardNumber: $('#id_card_number'),
|
||||
phoneNumberLabel: $('[for=id_phone_number]'),
|
||||
phoneNumberField: $('#id_phone_number'),
|
||||
cardNumberField: $('#id_card_number'),
|
||||
registerCardForm: $('.register-card-form'),
|
||||
registerButton: $('#register-submit-btn'),
|
||||
usernameField: $('#id_username'),
|
||||
userId: $('#id_user_id')
|
||||
userIdField: $('#id_user_id'),
|
||||
selectedUserWrap: $('.register-card-form--selected-user-wrap')
|
||||
};
|
||||
var urls = {
|
||||
insideUserApi: '/inside/user/',
|
||||
insidePhoneNumberApi: '/inside/phonenumber/',
|
||||
insideCardNumber: '/inside/cardnumber/'
|
||||
insideCardNumber: '/inside/cardnumber/',
|
||||
insideRegister: '/inside/register/'
|
||||
};
|
||||
|
||||
/* User search as you type */
|
||||
@@ -110,55 +182,61 @@ $(document).ready(function(){
|
||||
console.log(data);
|
||||
if(data.results && data.results.length > 0) {
|
||||
_dom.results.html(format_results(data.results));
|
||||
} else {
|
||||
users = data.results;
|
||||
}
|
||||
else if(data.error) {
|
||||
_dom.results.html(data.error);
|
||||
}
|
||||
else {
|
||||
if(val === "") {
|
||||
_dom.results.html('');
|
||||
} else {
|
||||
_dom.results.html('No existing user with that phone number');
|
||||
_dom.results.html("Found no existing user with search param: '"+ val +"'");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* Phone number as you type */
|
||||
_dom.phoneNumber.on('keyup', function() {
|
||||
var val = _dom.phoneNumber.val().trim();
|
||||
if(val.length === 0) {
|
||||
set_field_state(_dom.phoneNumber, '');
|
||||
return 'Phonenumber number should not be empty.';
|
||||
}
|
||||
$.getJSON(urls.insidePhoneNumberApi, {q: val}, function(data) {
|
||||
if(data.meta && data.meta.num_results == 1) {
|
||||
set_field_state(_dom.phoneNumber, 'success');
|
||||
_dom.userId.val(data.results[0].id);
|
||||
} else {
|
||||
set_field_state(_dom.phoneNumber, '');
|
||||
_dom.userId.val('');
|
||||
}
|
||||
});
|
||||
});
|
||||
/* Card number as you type */
|
||||
_dom.cardNumber.on('keyup', function() {
|
||||
if(!validate_form('cardNumber')) {
|
||||
_dom.phoneNumberField.on('input', function() {
|
||||
if( !validateCardForm() ) {
|
||||
return;
|
||||
}
|
||||
var val = _dom.phoneNumberField.val().trim();
|
||||
$.getJSON(urls.insidePhoneNumberApi, {q: val}, function(data) {
|
||||
if(data.meta && data.meta.num_results == 1) {
|
||||
set_field_state(_dom.phoneNumberField, 'success');
|
||||
set_selected_user(data.results[0], true);
|
||||
} else {
|
||||
set_field_state(_dom.phoneNumberField, '');
|
||||
set_selected_user('', true);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
/* Card number as you type */
|
||||
_dom.cardNumberField.on('input', function() {
|
||||
/* Card number should exist in the database and not tied to existing user */
|
||||
var val = _dom.cardNumber.val().trim();
|
||||
$.getJSON(urls.insideCardNumber, {q: val}, function(data) {
|
||||
if( !validateCardForm() ) {
|
||||
return;
|
||||
}
|
||||
var val = _dom.cardNumberField.val().trim();
|
||||
$.getJSON(urls.insideCardNumber, {card_number: val}, function(data) {
|
||||
if(data.error) {
|
||||
set_field_state(_dom.cardNumber, 'error', data.error);
|
||||
set_field_state(_dom.cardNumberField, 'error', data.error);
|
||||
return;
|
||||
}
|
||||
if(!data.valid) {
|
||||
set_field_state(_dom.cardNumber, 'error', 'Cannot find card number in database.');
|
||||
set_field_state(_dom.cardNumberField, 'error', 'Cannot find card number in database.');
|
||||
}
|
||||
else if(data.user !== null && data.valid) {
|
||||
set_field_state(_dom.cardNumber, 'error', 'Card number belongs to existing user: '+ data.user.firstname +''+ data.user.lastname +'('+data.user.id+').');
|
||||
var user = data.user[0];
|
||||
set_field_state(_dom.cardNumberField, 'error', 'Card number belongs to existing user: '+ user.firstname +' '+ user.lastname +' ('+user.id+').');
|
||||
}
|
||||
else if(data.user === null && data.valid) {
|
||||
set_field_state(_dom.cardNumber, 'success');
|
||||
set_field_state(_dom.cardNumberField, 'success');
|
||||
} else {
|
||||
set_field_state(_dom.cardNumber, '');
|
||||
set_field_state(_dom.cardNumberField, '');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -168,23 +246,89 @@ $(document).ready(function(){
|
||||
var label = $(this).parent();
|
||||
var number = label.attr('data-phone-number');
|
||||
var user_id = label.attr('data-user-id');
|
||||
|
||||
/* Update selected state */
|
||||
$('.search-result').removeClass('selected');
|
||||
label.toggleClass('selected');
|
||||
_dom.userId.val(user_id);
|
||||
|
||||
set_selected_user(_.findWhere(users, {id: user_id}));
|
||||
|
||||
/* Update phone number field */
|
||||
if(number === '-') {
|
||||
number = '';
|
||||
}
|
||||
_dom.phoneNumber.val(number);
|
||||
_dom.phoneNumberField.val(number);
|
||||
});
|
||||
|
||||
/* Initial state disabled */
|
||||
_dom.registerButton.attr('disabled','disabled');
|
||||
_dom.registerButton.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
// TODO on error output validation status
|
||||
// TODO on success:
|
||||
// - print what action was performed and suggest steps
|
||||
// - show new user below form (hide/show after 10s)
|
||||
// - clear form
|
||||
|
||||
if( !validateCardForm() ) {
|
||||
// ERROR
|
||||
return;
|
||||
}
|
||||
var url = urls.insideRegister;
|
||||
var payload = getFormData(_dom.registerCardForm);
|
||||
|
||||
/* Just updating card number? */
|
||||
if(payload.user_id && payload.card_number && selectedUser.is_member === "1") {
|
||||
url = urls.insideCardNumber;
|
||||
}
|
||||
$.ajax(url, {
|
||||
data: JSON.stringify(payload),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
type: 'post',
|
||||
headers: {'X-CSRFToken': getCookie('csrftoken')}
|
||||
}).success(function(data){
|
||||
console.log("success", data);
|
||||
|
||||
}).fail(function(data) {
|
||||
console.log("failed", data);
|
||||
});
|
||||
});
|
||||
|
||||
/* On user id change */
|
||||
_dom.userIdField.on('change', function() {
|
||||
/* Render selected user in search form */
|
||||
console.log("try updating selected user ", selectedUser);
|
||||
if(selectedUser) {
|
||||
render_selected_user({res: selectedUser});
|
||||
return;
|
||||
}
|
||||
render_selected_user({placeholder: true});
|
||||
});
|
||||
|
||||
/* Ninja add some icons to bootstrap form */
|
||||
var inputGroup = '<div class="input-group">';
|
||||
var iconMobile = '<span class="input-group-addon"><span class="glyphicon glyphicon-phone"></span></span>';
|
||||
_dom.phoneNumberField.wrap(inputGroup);
|
||||
_dom.phoneNumberField.before(iconMobile);
|
||||
|
||||
/* Initial focus */
|
||||
if(_dom.usernameField) {
|
||||
_dom.usernameField.focus();
|
||||
}
|
||||
if(_dom.phoneNumber) {
|
||||
_dom.phoneNumber.focus();
|
||||
if(_dom.phoneNumberField) {
|
||||
_dom.phoneNumberField.focus();
|
||||
}
|
||||
/* Initial submit button state disabled */
|
||||
_dom.registerButton.attr('disabled','disabled');
|
||||
|
||||
/* Load query params */
|
||||
/* FIXME: does not trigger validation and selected user preview */
|
||||
if( getParameterByName('user_id') ) {
|
||||
_dom.userIdField.val(getParameterByName('user_id')).trigger('change');
|
||||
}
|
||||
if( getParameterByName('phone_number') ) {
|
||||
_dom.phoneNumberField.val(getParameterByName('phone_number'));
|
||||
}
|
||||
if( getParameterByName('card_number') ) {
|
||||
_dom.cardNumberField.val(getParameterByName('card_number'));
|
||||
}
|
||||
});
|
||||
Vendored
+39
-23
@@ -3,8 +3,10 @@ var lineno = null;
|
||||
var colno = null;
|
||||
var output = "";
|
||||
try {
|
||||
if(!runtime.contextOrFrameLookup(context, frame, "placeholder")) {
|
||||
output += "\n ";
|
||||
if(!runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"cardno", env.opts.autoescape) || runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"card_is_legacy", env.opts.autoescape)) {
|
||||
output += "\n <label for=\"";
|
||||
output += "\n <label for=\"";
|
||||
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id", env.opts.autoescape), env.opts.autoescape);
|
||||
output += "\" data-phone-number=\"";
|
||||
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"number", env.opts.autoescape), env.opts.autoescape);
|
||||
@@ -19,13 +21,18 @@ else {
|
||||
output += " not-member";
|
||||
;
|
||||
}
|
||||
output += "\">\n <input type=\"radio\" name=\"user\" id=\"";
|
||||
output += "\">\n <input type=\"radio\" name=\"user\" id=\"";
|
||||
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"id", env.opts.autoescape), env.opts.autoescape);
|
||||
output += "\">\n";
|
||||
output += "\"";
|
||||
if(runtime.contextOrFrameLookup(context, frame, "checked")) {
|
||||
output += " checked";
|
||||
;
|
||||
}
|
||||
output += ">\n ";
|
||||
;
|
||||
}
|
||||
else {
|
||||
output += "\n <!-- Has card -->\n <div class=\"list-group-item search-result has-card";
|
||||
output += "\n <!-- Has card -->\n <div class=\"list-group-item search-result has-card";
|
||||
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_member", env.opts.autoescape)) {
|
||||
output += " member";
|
||||
;
|
||||
@@ -34,56 +41,65 @@ else {
|
||||
output += "not-member";
|
||||
;
|
||||
}
|
||||
output += "\">\n";
|
||||
output += "\">\n ";
|
||||
;
|
||||
}
|
||||
output += "\n<!-- Name -->\n";
|
||||
output += "\n <!-- Name -->\n ";
|
||||
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"firstname", env.opts.autoescape), env.opts.autoescape);
|
||||
output += " ";
|
||||
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"lastname", env.opts.autoescape), env.opts.autoescape);
|
||||
output += "\n<div class=\"labels\">\n <!-- Membership -->\n ";
|
||||
output += "\n <div class=\"labels\">\n <!-- Membership -->\n ";
|
||||
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_member", env.opts.autoescape) == "1") {
|
||||
output += "\n <span class=\"label label-success\">Member: ";
|
||||
output += "\n <span class=\"label label-success\">Member: ";
|
||||
output += runtime.suppressValue(env.getFilter("default").call(context, runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"expires", env.opts.autoescape),"Lifelong"), env.opts.autoescape);
|
||||
output += "</span>\n ";
|
||||
output += "</span>\n ";
|
||||
;
|
||||
}
|
||||
else {
|
||||
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"expires", env.opts.autoescape) == "0000-00-00") {
|
||||
output += "\n <span class=\"label label-default\">Registered</span>\n ";
|
||||
output += "\n <span class=\"label label-default\">Registered</span>\n ";
|
||||
;
|
||||
}
|
||||
else {
|
||||
output += "\n <span class=\"label label-warning\">Expired: ";
|
||||
output += "\n <span class=\"label label-warning\">Expired: ";
|
||||
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"expires", env.opts.autoescape), env.opts.autoescape);
|
||||
output += "</span>\n ";
|
||||
output += "</span>\n ";
|
||||
;
|
||||
}
|
||||
;
|
||||
}
|
||||
output += "\n <!-- Card -->\n ";
|
||||
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"cardno", env.opts.autoescape) && !runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"card_is_legacy", env.opts.autoescape)) {
|
||||
output += "\n <span class=\"label label-card \"><span class=\"glyphicon glyphicon-user\"></span> ";
|
||||
output += runtime.suppressValue(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"cardno", env.opts.autoescape), env.opts.autoescape);
|
||||
output += "</span>\n ";
|
||||
output += "\n <!-- Card -->\n ";
|
||||
if(!runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"card_is_legacy", env.opts.autoescape)) {
|
||||
output += "\n <span class=\"label label-card \"><span class=\"glyphicon glyphicon-user\"></span> ";
|
||||
output += runtime.suppressValue(env.getFilter("default").call(context, runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"cardno", env.opts.autoescape),"No card"), env.opts.autoescape);
|
||||
output += "</span>\n ";
|
||||
;
|
||||
}
|
||||
else {
|
||||
output += "\n <span class=\"label label-card \"><span class=\"glyphicon glyphicon-user\"></span> No card</span>\n ";
|
||||
output += "\n <span class=\"label label-card \"><span class=\"glyphicon glyphicon-user\"></span> No card</span>\n ";
|
||||
;
|
||||
}
|
||||
output += "\n <!-- Is active -->\n ";
|
||||
output += "\n <!-- Is active -->\n ";
|
||||
if(runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"is_active", env.opts.autoescape) == "1") {
|
||||
output += "\n <span class=\"label label-is-active\">Active</span>\n ";
|
||||
output += "\n <span class=\"label label-is-active\">Active</span>\n ";
|
||||
;
|
||||
}
|
||||
output += "\n</div>\n";
|
||||
output += "\n </div>\n ";
|
||||
if(!runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"cardno", env.opts.autoescape) || runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"card_is_legacy", env.opts.autoescape)) {
|
||||
output += "\n <div class=\"selected-icon\"><span class=\"glyphicon glyphicon-ok\"></span></div>\n </label>\n";
|
||||
output += "\n <div class=\"selected-icon\"><span class=\"glyphicon glyphicon-ok\"></span></div>\n </label>\n ";
|
||||
;
|
||||
}
|
||||
else {
|
||||
output += "\n </div>\n";
|
||||
output += "\n </div>\n ";
|
||||
;
|
||||
}
|
||||
output += "\n";
|
||||
;
|
||||
}
|
||||
else {
|
||||
output += "\n <!-- Placeholder -->\n <div class=\"list-group-item search-result placeholder\">\n <input type=\"radio\" name=\"user\" checked>\n <em>New</em>\n <div class=\"labels\">\n <span class=\"label label-card \"><span class=\"glyphicon glyphicon-user\"></span> ";
|
||||
output += runtime.suppressValue(env.getFilter("default").call(context, runtime.memberLookup((runtime.contextOrFrameLookup(context, frame, "res")),"cardno", env.opts.autoescape),"New card"), env.opts.autoescape);
|
||||
output += "</span>\n </div>\n </div>\n";
|
||||
;
|
||||
}
|
||||
cb(null, output);
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
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,menu,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-color:transparent}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{*,*:before,*:after{background:transparent!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]: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}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url("../fonts/glyphicons-halflings-regular.eot");src:url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("../fonts/glyphicons-halflings-regular.woff") format("woff"),url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("../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,.glyphicon-eur: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"}.gLine truncated
|
||||
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,menu,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-color:transparent}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{*,*:before,*:after{background:transparent!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^="#"]:after,a[href^="javascript:"]: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}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url("../fonts/glyphicons-halflings-regular.eot");src:url("../fonts/glyphicons-halflings-regular.eot?#iefix") format("embedded-opentype"),url("../fonts/glyphicons-halflings-regular.woff2") format("woff2"),url("../fonts/glyphicons-halflings-regular.woff") format("woff"),url("../fonts/glyphicons-halflings-regular.ttf") format("truetype"),url("../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,.glyphicon-eur: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"}.gLine truncated
|
||||
Reference in new issue
Block a user