Purge even more jquery stuff

and related static files etc. from old themes

this might be the end of it..??
This commit is contained in:
Lance Edgar 2023-02-03 17:08:33 -06:00
parent 2ebae17839
commit 976a5836a9
42 changed files with 366 additions and 5152 deletions

View file

@ -1,122 +1,14 @@
/******************************
* General
******************************/
* {
margin: 0px;
}
body {
font-family: Verdana, Arial, sans-serif;
font-size: 11pt;
}
a {
color: #0972a5;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
margin-bottom: 15px;
}
h2 {
font-size: 12pt;
margin: 20px auto 10px auto;
}
li {
line-height: 2em;
}
p {
margin-bottom: 5px;
}
.left {
float: left;
text-align: left;
}
.right {
text-align: right;
}
.wrapper {
overflow: auto;
}
div.buttons {
clear: both;
margin-top: 10px;
}
div.dialog {
display: none;
}
div.flash-message {
background-color: #dddddd;
margin-bottom: 8px;
padding: 3px;
}
div.flash-messages div.ui-state-highlight {
padding: .3em;
margin-bottom: 8px;
}
div.error-messages div.ui-state-error {
padding: .3em;
margin-bottom: 8px;
}
.flash-messages,
.error-messages {
margin: 0.5em 0 0 0;
}
ul.error {
color: #dd6666;
font-weight: bold;
padding: 0px;
}
ul.error li {
list-style-type: none;
}
pre.is-family-sans-serif {
background-color: white;
font-family: Verdana, Arial, sans-serif;
font-size: 11pt;
padding: 1em;
}
/******************************
* jQuery UI tweaks
******************************/
ul.ui-menu {
max-height: 30em;
}
/******************************
* tweaks for root user
******************************/
.menubar .root-user .ui-button-text,
.menubar .root-user.ui-menu-item a {
.navbar .navbar-end .navbar-link.root-user,
.navbar .navbar-end .navbar-link.root-user:hover,
.navbar .navbar-end .navbar-link.root-user.is_active,
.navbar .navbar-end .navbar-item.root-user,
.navbar .navbar-end .navbar-item.root-user:hover,
.navbar .navbar-end .navbar-item.root-user.is_active {
background-color: red;
color: black;
font-weight: bold;
}
.menubar .root-user.ui-menu-item a {
padding-left: 1em;
}

View file

@ -1,28 +1,22 @@
/******************************
* Filters
* Grid Filters
******************************/
div.filters form {
margin-bottom: 10px;
.filters .filter {
margin-bottom: 0.5rem;
}
div.filters div.filter {
margin-bottom: 10px;
.filters .filter-fieldname .field,
.filters .filter-fieldname .field label {
width: 100%;
}
div.filters div.filter label {
margin-right: 8px;
.filters .filter-fieldname .field label {
justify-content: left;
}
div.filters div.filter select.filter-type {
margin-right: 8px;
}
div.filters div.filter div.value {
display: inline;
}
div.filters div.buttons * {
margin-right: 8px;
.filters .filter-verb .select,
.filters .filter-verb .select select {
width: 100%;
}

View file

@ -1,34 +1,37 @@
/******************************
* Form Wrapper
* forms
******************************/
div.form-wrapper {
overflow: auto;
}
/******************************
* Forms
******************************/
div.form,
div.fieldset-form,
div.fieldset {
clear: left;
float: left;
margin-top: 10px;
}
/* note that this should only apply to "normal" primary forms */
/* TODO: replace this with bulma equivalent */
.form {
padding-left: 5em;
}
/* note that this should only apply to "normal" primary forms */
.form-wrapper .form .field.is-horizontal .field-label .label {
text-align: left;
white-space: nowrap;
width: 18em;
}
/* note that this should only apply to "normal" primary forms */
.form-wrapper .form .field.is-horizontal .field-body {
min-width: 30em;
}
/* note that this should only apply to "normal" primary forms */
.form-wrapper .form .field.is-horizontal .field-body .select,
.form-wrapper .form .field.is-horizontal .field-body .select select {
width: 100%;
}
/******************************
* Fieldsets
* field-wrappers
******************************/
/* TODO: replace this with bulma equivalent */
.field-wrapper {
clear: both;
min-height: 30px;
@ -36,16 +39,12 @@ div.fieldset {
margin: 15px;
}
.field-wrapper.with-error {
background-color: #ddcccc;
border: 2px solid #dd6666;
padding-bottom: 1em;
}
/* TODO: replace this with bulma equivalent */
.field-wrapper .field-row {
display: table-row;
}
/* TODO: replace this with bulma equivalent */
.field-wrapper label {
display: table-cell;
vertical-align: top;
@ -55,47 +54,8 @@ div.fieldset {
white-space: nowrap;
}
.field-wrapper.with-error label {
padding-left: 1em;
}
.field-wrapper .field-error {
padding: 1em 0 0.5em 1em;
}
.field-wrapper .field-error .error-msg {
color: #dd6666;
font-weight: bold;
}
/* TODO: replace this with bulma equivalent */
.field-wrapper .field {
display: table-cell;
line-height: 25px;
}
.field-wrapper .field input[type=text],
.field-wrapper .field input[type=password],
.field-wrapper .field select,
.field-wrapper .field textarea {
width: 320px;
}
label input[type="checkbox"],
label input[type="radio"] {
margin-right: 0.5em;
}
.field ul {
margin: 0px;
padding-left: 15px;
}
/******************************
* Buttons
******************************/
div.buttons {
clear: both;
margin: 10px 0px;
}

View file

@ -261,6 +261,10 @@
* main actions
******************************/
a.grid-action {
white-space: nowrap;
}
.grid .actions {
width: 1px;
}

View file

@ -1,40 +0,0 @@
.loadmask {
z-index: 100;
position: absolute;
top:0;
left:0;
-moz-opacity: 0.5;
opacity: .50;
filter: alpha(opacity=50);
background-color: #CCC;
width: 100%;
height: 100%;
zoom: 1;
}
.loadmask-msg {
z-index: 20001;
position: absolute;
top: 0;
left: 0;
border:1px solid #6593cf;
background: #c3daf9;
padding:2px;
}
.loadmask-msg div {
padding:5px 10px 5px 25px;
background: #fbfbfb url('../img/loading.gif') no-repeat 5px 5px;
line-height: 16px;
border:1px solid #a3bad9;
color:#222;
font:normal 11px tahoma, arial, helvetica, sans-serif;
cursor:wait;
}
.masked {
overflow: hidden !important;
}
.masked-relative {
position: relative !important;
}
.masked-hidden {
visibility: hidden !important;
}

View file

@ -1,15 +0,0 @@
/*
* jQuery UI Menubar @VERSION
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
.ui-menubar { list-style: none; margin: 0; padding-left: 0; }
.ui-menubar-item { float: left; }
.ui-menubar .ui-button { float: left; font-weight: normal; border-top-width: 0 !important; border-bottom-width: 0 !important; margin: 0; outline: none; }
.ui-menubar .ui-menubar-link { border-right: 1px dashed transparent; border-left: 1px dashed transparent; }
.ui-menubar .ui-menu { width: 200px; position: absolute; z-index: 9999; font-weight: normal; }

View file

@ -1,14 +0,0 @@
/**********************************************************************
* jquery.ui.tailbone.css
*
* jQuery UI tweaks for Tailbone
**********************************************************************/
.ui-widget {
font-size: 1em;
}
.ui-menu-item a {
display: block;
}

View file

@ -1,57 +0,0 @@
/*
* Timepicker stylesheet
* Highly inspired from datepicker
* FG - Nov 2010 - Web3R
*
* version 0.0.3 : Fixed some settings, more dynamic
* version 0.0.4 : Removed width:100% on tables
* version 0.1.1 : set width 0 on tables to fix an ie6 bug
*/
.ui-timepicker-inline { display: inline; }
#ui-timepicker-div { padding: 0.2em; }
.ui-timepicker-table { display: inline-table; width: 0; }
.ui-timepicker-table table { margin:0.15em 0 0 0; border-collapse: collapse; }
.ui-timepicker-hours, .ui-timepicker-minutes { padding: 0.2em; }
.ui-timepicker-table .ui-timepicker-title { line-height: 1.8em; text-align: center; }
.ui-timepicker-table td { padding: 0.1em; width: 2.2em; }
.ui-timepicker-table th.periods { padding: 0.1em; width: 2.2em; }
/* span for disabled cells */
.ui-timepicker-table td span {
display:block;
padding:0.2em 0.3em 0.2em 0.5em;
width: 1.2em;
text-align:right;
text-decoration:none;
}
/* anchors for clickable cells */
.ui-timepicker-table td a {
display:block;
padding:0.2em 0.3em 0.2em 0.5em;
width: 1.2em;
cursor: pointer;
text-align:right;
text-decoration:none;
}
/* buttons and button pane styling */
.ui-timepicker .ui-timepicker-buttonpane {
background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0;
}
.ui-timepicker .ui-timepicker-buttonpane button { margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
/* The close button */
.ui-timepicker .ui-timepicker-close { float: right }
/* the now button */
.ui-timepicker .ui-timepicker-now { float: left; }
/* the deselect button */
.ui-timepicker .ui-timepicker-deselect { float: left; }

View file

@ -1,152 +1,90 @@
/******************************
* Main Layout
* main layout
******************************/
html, body, #body-wrapper {
height: 100%;
}
body > #body-wrapper {
height: auto;
min-height: 100%;
}
#body-wrapper {
margin: 0 1em;
width: auto;
}
#header {
height: 50px;
line-height: 50px;
}
#body {
padding-top: 10px;
padding-bottom: 5em;
}
#footer {
clear: both;
margin-top: -4em;
text-align: center;
}
/******************************
* Header
******************************/
#header h1 {
float: left;
font-size: 25px;
margin: 0px;
}
#header div.login {
float: right;
}
/* new stuff from 'better' theme begins here */
header .global {
background-color: #eaeaea;
height: 60px;
}
header .global a.home,
header .global a.global,
header .global span.global {
display: block;
float: left;
font-size: 2em;
font-weight: bold;
line-height: 60px;
margin-left: 10px;
}
header .global a.home img {
display: block;
float: left;
padding: 5px 5px 5px 30px;
}
header .global .grid-nav {
display: inline-block;
font-size: 16px;
font-weight: bold;
line-height: 60px;
margin-left: 5em;
}
header .global .grid-nav .ui-button,
header .global .grid-nav span.viewing {
margin-left: 1em;
}
header .global .feedback {
float: right;
line-height: 60px;
margin-right: 1em;
}
header .global .after-feedback {
float: right;
line-height: 60px;
margin-right: 1em;
}
header .page {
border-bottom: 1px solid lightgrey;
padding: 0.5em;
}
header .page h1 {
margin: 0;
padding: 0 0 0 0.5em;
}
/******************************
* Logo
******************************/
#logo {
display: block;
margin: 40px auto;
}
/****************************************
* content
****************************************/
body > #body-wrapper {
margin: 0px;
position: relative;
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.content-wrapper {
height: 100%;
padding-bottom: 30px;
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-between;
}
#scrollpane {
height: 100%;
/******************************
* header
******************************/
/* this is the one in the very top left of screen, next to logo and linked to
the home page */
#global-header-title {
margin-left: 0.3rem;
}
#scrollpane .inner-content {
padding: 0 0.5em 0.5em 0.5em;
header .level {
/* TODO: not sure what this 60px was supposed to do? but it broke the */
/* styles for the feedback dialog, so disabled it is.
/* height: 60px; */
/* line-height: 60px; */
padding-left: 0.5em;
padding-right: 0.5em;
}
header .level #header-logo {
display: inline-block;
}
header .level .global-title,
header .level-left .global-title {
font-size: 2em;
font-weight: bold;
}
/* indent nested menu items a bit */
header .navbar-item.nested {
padding-left: 2.5rem;
}
header span.header-text {
font-size: 2em;
font-weight: bold;
margin-right: 10px;
}
header .level .theme-picker {
display: inline-flex;
}
#content-title {
padding: 0.3rem;
}
#content-title h1 {
font-size: 2rem;
margin-left: 1rem;
}
/******************************
* content
******************************/
#page-body {
padding: 0.4em;
}
/******************************
* context menu
******************************/
#context-menu {
list-style-type: none;
margin: 0.5em;
margin-bottom: 1em;
margin-left: 1em;
text-align: right;
white-space: nowrap;
}
@ -155,11 +93,19 @@ body > #body-wrapper {
* "object helper" panel
******************************/
.object-helpers .panel-heading {
white-space: nowrap;
}
.object-helpers a {
white-space: nowrap;
}
.object-helper {
border: 1px solid black;
margin: 1em;
padding: 1em;
min-width: 20em;
width: 20em;
}
.object-helper-content {
@ -167,87 +113,38 @@ body > #body-wrapper {
}
/******************************
* Panels
* markdown
******************************/
.panel,
.panel-grid {
border-left: 1px solid Black;
margin-bottom: 1em;
.rendered-markdown p,
.rendered-markdown ul {
margin-bottom: 1rem;
}
.panel {
border-bottom: 1px solid Black;
border-right: 1px solid Black;
padding: 0px;
.rendered-markdown .codehilite {
margin-bottom: 2rem;
}
.panel h2,
.panel-grid h2 {
border-bottom: 1px solid Black;
border-top: 1px solid Black;
padding: 5px;
margin: 0px;
/******************************
* fix datepicker within modals
* TODO: someday this may not be necessary? cf.
* https://github.com/buefy/buefy/issues/292#issuecomment-347365637
******************************/
.modal .animation-content .modal-card {
overflow: visible !important;
}
.panel-grid h2 {
border-right: 1px solid Black;
.modal-card-body {
overflow: visible !important;
}
.panel-body {
overflow: auto;
padding: 5px;
}
/****************************************
* footer
****************************************/
#footer {
border-top: 1px solid lightgray;
bottom: 0;
font-size: 9pt;
height: 20px;
left: 0;
line-height: 20px;
margin: 0;
position: absolute;
width: 100%;
}
/******************************
* feedback
******************************/
#feedback-dialog {
display: none;
}
#feedback-dialog p {
margin-top: 1em;
}
#feedback-dialog .red {
.feedback-dialog .red {
color: red;
font-weight: bold;
}
#feedback-dialog .field-wrapper {
margin-top: 1em;
padding: 0;
}
#feedback-dialog .field {
margin-bottom: 0;
margin-top: 0.5em;
}
#feedback-dialog .referrer .field {
clear: both;
float: none;
margin-top: 1em;
}
#feedback-dialog textarea {
width: auto;
}

View file

@ -1,452 +0,0 @@
/**********************************************************************
* jQuery UI plugins for Tailbone
**********************************************************************/
/**********************************************************************
* gridcore plugin
**********************************************************************/
(function($) {
$.widget('tailbone.gridcore', {
_create: function() {
var that = this;
// Add hover highlight effect to grid rows during mouse-over.
// this.element.on('mouseenter', 'tbody tr:not(.header)', function() {
this.element.on('mouseenter', 'tr:not(.header)', function() {
$(this).addClass('hovering');
});
// this.element.on('mouseleave', 'tbody tr:not(.header)', function() {
this.element.on('mouseleave', 'tr:not(.header)', function() {
$(this).removeClass('hovering');
});
// do some extra stuff for grids with checkboxes
// mark rows selected on page load, as needed
this.element.find('tr:not(.header) td.checkbox :checkbox:checked').each(function() {
$(this).parents('tr:first').addClass('selected');
});
// (un-)check all rows when clicking check-all box in header
if (this.element.find('tr.header td.checkbox :checkbox').length) {
this.element.on('click', 'tr.header td.checkbox :checkbox', function() {
var checked = $(this).prop('checked');
var rows = that.element.find('tr:not(.header)');
rows.find('td.checkbox :checkbox').prop('checked', checked);
if (checked) {
rows.addClass('selected');
} else {
rows.removeClass('selected');
}
that.element.trigger('gridchecked', that.count_selected());
});
}
// when row with checkbox is clicked, toggle selected status,
// unless clicking checkbox (since that already toggles it) or a
// link (since that does something completely different)
this.element.on('click', 'tr:not(.header)', function(event) {
var el = $(event.target);
if (!el.is('a') && !el.is(':checkbox')) {
$(this).find('td.checkbox :checkbox').click();
}
});
this.element.on('change', 'tr:not(.header) td.checkbox :checkbox', function() {
if (this.checked) {
$(this).parents('tr:first').addClass('selected');
} else {
$(this).parents('tr:first').removeClass('selected');
}
that.element.trigger('gridchecked', that.count_selected());
});
// Show 'more' actions when user hovers over 'more' link.
this.element.on('mouseenter', '.actions a.more', function() {
that.element.find('.actions div.more').hide();
$(this).siblings('div.more')
.show()
.position({my: 'left-5 top-4', at: 'left top', of: $(this)});
});
this.element.on('mouseleave', '.actions div.more', function() {
$(this).hide();
});
// Add speed bump for "Delete Row" action, if grid is so configured.
if (this.element.data('delete-speedbump')) {
this.element.on('click', 'tr:not(.header) .actions a.delete', function() {
return confirm("Are you sure you wish to delete this object?");
});
}
},
count_selected: function() {
return this.element.find('tr:not(.header) td.checkbox :checkbox:checked').length;
},
// TODO: deprecate / remove this?
count_checked: function() {
return this.count_selected();
},
selected_rows: function() {
return this.element.find('tr:not(.header) td.checkbox :checkbox:checked').parents('tr:first');
},
all_uuids: function() {
var uuids = [];
this.element.find('tr:not(.header)').each(function() {
uuids.push($(this).data('uuid'));
});
return uuids;
},
selected_uuids: function() {
var uuids = [];
this.element.find('tr:not(.header) td.checkbox :checkbox:checked').each(function() {
uuids.push($(this).parents('tr:first').data('uuid'));
});
return uuids;
}
});
})( jQuery );
/**********************************************************************
* gridwrapper plugin
**********************************************************************/
(function($) {
$.widget('tailbone.gridwrapper', {
_create: function() {
var that = this;
// Snag some element references.
this.filters = this.element.find('.newfilters');
this.filters_form = this.filters.find('form');
this.add_filter = this.filters.find('#add-filter');
this.apply_filters = this.filters.find('#apply-filters');
this.default_filters = this.filters.find('#default-filters');
this.clear_filters = this.filters.find('#clear-filters');
this.save_defaults = this.filters.find('#save-defaults');
this.grid = this.element.find('.grid');
// add standard grid behavior
this.grid.gridcore();
// Enhance filters etc.
this.filters.find('.filter').gridfilter();
this.apply_filters.button('option', 'icons', {primary: 'ui-icon-search'});
this.default_filters.button('option', 'icons', {primary: 'ui-icon-home'});
this.clear_filters.button('option', 'icons', {primary: 'ui-icon-trash'});
this.save_defaults.button('option', 'icons', {primary: 'ui-icon-disk'});
if (! this.filters.find('.active:checked').length) {
this.apply_filters.button('disable');
}
this.add_filter.selectmenu({
width: '15em',
// Initially disabled if contains no enabled filter options.
disabled: this.add_filter.find('option:enabled').length == 1,
// When add-filter choice is made, show/focus new filter value input,
// and maybe hide the add-filter selection or show the apply button.
change: function (event, ui) {
var filter = that.filters.find('#filter-' + ui.item.value);
var select = $(this);
var option = ui.item.element;
filter.gridfilter('active', true);
filter.gridfilter('focus');
select.val('');
option.attr('disabled', 'disabled');
select.selectmenu('refresh');
if (select.find('option:enabled').length == 1) { // prompt is always enabled
select.selectmenu('disable');
}
that.apply_filters.button('enable');
}
});
this.add_filter.on('selectmenuopen', function(event, ui) {
show_all_options($(this));
});
// Intercept filters form submittal, and submit via AJAX instead.
this.filters_form.on('submit', function() {
var settings = {filter: true, partial: true};
if (that.filters_form.find('input[name="save-current-filters-as-defaults"]').val() == 'true') {
settings['save-current-filters-as-defaults'] = true;
}
that.filters.find('.filter').each(function() {
// currently active filters will be included in form data
if ($(this).gridfilter('active')) {
settings[$(this).data('key')] = $(this).gridfilter('value');
settings[$(this).data('key') + '.verb'] = $(this).gridfilter('verb');
// others will be hidden from view
} else {
$(this).gridfilter('hide');
}
});
// if no filters are visible, disable submit button
if (! that.filters.find('.filter:visible').length) {
that.apply_filters.button('disable');
}
// okay, submit filters to server and refresh grid
that.refresh(settings);
return false;
});
// When user clicks Default Filters button, refresh page with
// instructions for the server to reset filters to default settings.
this.default_filters.click(function() {
that.filters_form.off('submit');
that.filters_form.find('input[name="reset-to-default-filters"]').val('true');
that.element.mask("Refreshing data...");
that.filters_form.get(0).submit();
});
// When user clicks Save Defaults button, refresh the grid as with
// Apply Filters, but add an instruction for the server to save
// current settings as defaults for the user.
this.save_defaults.click(function() {
that.filters_form.find('input[name="save-current-filters-as-defaults"]').val('true');
that.filters_form.submit();
that.filters_form.find('input[name="save-current-filters-as-defaults"]').val('false');
});
// When user clicks Clear Filters button, deactivate all filters
// and refresh the grid.
this.clear_filters.click(function() {
that.filters.find('.filter').each(function() {
if ($(this).gridfilter('active')) {
$(this).gridfilter('active', false);
}
});
that.filters_form.submit();
});
// Refresh data when user clicks a sortable column header.
this.element.on('click', 'tr.header a', function() {
var td = $(this).parent();
var data = {
sortkey: $(this).data('sortkey'),
sortdir: (td.hasClass('asc')) ? 'desc' : 'asc',
page: 1,
partial: true
};
that.refresh(data);
return false;
});
// Refresh data when user chooses a new page size setting.
this.element.on('change', '.pager #pagesize', function() {
var settings = {
partial: true,
pagesize: $(this).val()
};
that.refresh(settings);
});
// Refresh data when user clicks a pager link.
this.element.on('click', '.pager a', function() {
that.refresh(this.search.substring(1)); // remove leading '?'
return false;
});
},
// Refreshes the visible data within the grid, according to the given settings.
refresh: function(settings) {
var that = this;
this.element.mask("Refreshing data...");
$.get(this.grid.data('url'), settings, function(data) {
that.grid.replaceWith(data);
that.grid = that.element.find('.grid');
that.grid.gridcore();
that.element.unmask();
});
},
results_count: function(as_text) {
var count = null;
var match = /showing \d+ thru \d+ of (\S+)/.exec(this.element.find('.pager .showing').text());
if (match) {
count = match[1];
if (!as_text) {
count = parseInt(count, 10);
}
}
return count;
},
all_uuids: function() {
return this.grid.gridcore('all_uuids');
},
selected_uuids: function() {
return this.grid.gridcore('selected_uuids');
}
});
})( jQuery );
/**********************************************************************
* gridfilter plugin
**********************************************************************/
(function($) {
$.widget('tailbone.gridfilter', {
_create: function() {
var that = this;
// Track down some important elements.
this.checkbox = this.element.find('input[name$="-active"]');
this.label = this.element.find('label');
this.inputs = this.element.find('.inputs');
this.add_filter = this.element.parents('.grid-wrapper').find('#add-filter');
// Hide the checkbox and label, and add button for toggling active status.
this.checkbox.addClass('ui-helper-hidden-accessible');
this.label.hide();
this.activebutton = $('<button type="button" class="toggle" />')
.insertAfter(this.label)
.text(this.label.text())
.button({
icons: {primary: 'ui-icon-blank'}
});
// Enhance verb dropdown as selectmenu.
this.verb_select = this.inputs.find('.verb');
this.valueless_verbs = {};
$.each(this.verb_select.data('hide-value-for').split(' '), function(index, value) {
that.valueless_verbs[value] = true;
});
this.verb_select.selectmenu({
width: '15em',
change: function(event, ui) {
if (ui.item.value in that.valueless_verbs) {
that.inputs.find('.value').hide();
} else {
that.inputs.find('.value').show();
that.focus();
that.select();
}
}
});
this.verb_select.on('selectmenuopen', function(event, ui) {
show_all_options($(this));
});
// Enhance any date values with datepicker widget.
this.inputs.find('.value input[data-datepicker="true"]').datepicker({
dateFormat: 'yy-mm-dd',
changeYear: true,
changeMonth: true
});
// Enhance any choice/dropdown values with selectmenu.
this.inputs.find('.value select').selectmenu({
// provide sane width for value dropdown
width: '15em'
});
this.inputs.find('.value select').on('selectmenuopen', function(event, ui) {
show_all_options($(this));
});
// Listen for button click, to keep checkbox in sync.
this._on(this.activebutton, {
click: function(e) {
var checked = !this.checkbox.is(':checked');
this.checkbox.prop('checked', checked);
this.refresh();
if (checked) {
this.focus();
}
}
});
// Update the initial state of the button according to checkbox.
this.refresh();
},
refresh: function() {
if (this.checkbox.is(':checked')) {
this.activebutton.button('option', 'icons', {primary: 'ui-icon-check'});
if (this.verb() in this.valueless_verbs) {
this.inputs.find('.value').hide();
} else {
this.inputs.find('.value').show();
}
this.inputs.show();
} else {
this.activebutton.button('option', 'icons', {primary: 'ui-icon-blank'});
this.inputs.hide();
}
},
active: function(value) {
if (value === undefined) {
return this.checkbox.is(':checked');
}
if (value) {
if (!this.checkbox.is(':checked')) {
this.checkbox.prop('checked', true);
this.refresh();
this.element.show();
}
} else if (this.checkbox.is(':checked')) {
this.checkbox.prop('checked', false);
this.refresh();
}
},
hide: function() {
this.active(false);
this.element.hide();
var option = this.add_filter.find('option[value="' + this.element.data('key') + '"]');
option.attr('disabled', false);
if (this.add_filter.selectmenu('option', 'disabled')) {
this.add_filter.selectmenu('enable');
}
this.add_filter.selectmenu('refresh');
},
focus: function() {
this.inputs.find('.value input').focus();
},
select: function() {
this.inputs.find('.value input').select();
},
value: function() {
return this.inputs.find('.value input, .value select').val();
},
verb: function() {
return this.inputs.find('.verb').val();
}
});
})( jQuery );

View file

@ -1,10 +0,0 @@
/**
* Copyright (c) 2009 Sergiy Kovalchuk (serg472@gmail.com)
*
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*
* Following code is based on Element.mask() implementation from ExtJS framework (http://extjs.com/)
*
*/
(function(a){a.fn.mask=function(c,b){a(this).each(function(){if(b!==undefined&&b>0){var d=a(this);d.data("_mask_timeout",setTimeout(function(){a.maskElement(d,c)},b))}else{a.maskElement(a(this),c)}})};a.fn.unmask=function(){a(this).each(function(){a.unmaskElement(a(this))})};a.fn.isMasked=function(){return this.hasClass("masked")};a.maskElement=function(d,c){if(d.data("_mask_timeout")!==undefined){clearTimeout(d.data("_mask_timeout"));d.removeData("_mask_timeout")}if(d.isMasked()){a.unmaskElement(d)}if(d.css("position")=="static"){d.addClass("masked-relative")}d.addClass("masked");var e=a('<div class="loadmask"></div>');if(navigator.userAgent.toLowerCase().indexOf("msie")>-1){e.height(d.height()+parseInt(d.css("padding-top"))+parseInt(d.css("padding-bottom")));e.width(d.width()+parseInt(d.css("padding-left"))+parseInt(d.css("padding-right")))}if(navigator.userAgent.toLowerCase().indexOf("msie 6")>-1){d.find("select").addClass("masked-hidden")}d.append(e);if(c!==undefined){var b=a('<div class="loadmask-msg" style="display:none;"></div>');b.append("<div>"+c+"</div>");d.append(b);b.css("top",Math.round(d.height()/2-(b.height()-parseInt(b.css("padding-top"))-parseInt(b.css("padding-bottom")))/2)+"px");b.css("left",Math.round(d.width()/2-(b.width()-parseInt(b.css("padding-left"))-parseInt(b.css("padding-right")))/2)+"px");b.show()}};a.unmaskElement=function(b){if(b.data("_mask_timeout")!==undefined){clearTimeout(b.data("_mask_timeout"));b.removeData("_mask_timeout")}b.find(".loadmask-msg,.loadmask").remove();b.removeClass("masked");b.removeClass("masked-relative");b.find("select").removeClass("masked-hidden")}})(jQuery);

View file

@ -1,331 +0,0 @@
/*
* jQuery UI Menubar @VERSION
*
* Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Menubar
*
* Depends:
* jquery.ui.core.js
* jquery.ui.widget.js
* jquery.ui.position.js
* jquery.ui.menu.js
*/
(function( $ ) {
// TODO when mixing clicking menus and keyboard navigation, focus handling is broken
// there has to be just one item that has tabindex
$.widget( "ui.menubar", {
version: "@VERSION",
options: {
autoExpand: false,
buttons: false,
items: "li",
menuElement: "ul",
menuIcon: false,
position: {
my: "left top",
at: "left bottom"
}
},
_create: function() {
var that = this;
this.menuItems = this.element.children( this.options.items );
this.items = this.menuItems.children( "button, a" );
this.menuItems
.addClass( "ui-menubar-item" )
.attr( "role", "presentation" );
// let only the first item receive focus
this.items.slice(1).attr( "tabIndex", -1 );
this.element
.addClass( "ui-menubar ui-widget-header ui-helper-clearfix" )
.attr( "role", "menubar" );
this._focusable( this.items );
this._hoverable( this.items );
this.items.siblings( this.options.menuElement )
.menu({
position: {
within: this.options.position.within
},
select: function( event, ui ) {
ui.item.parents( "ul.ui-menu:last" ).hide();
that._close();
// TODO what is this targetting? there's probably a better way to access it
$(event.target).prev().focus();
that._trigger( "select", event, ui );
},
menus: that.options.menuElement
})
.hide()
.attr({
"aria-hidden": "true",
"aria-expanded": "false"
})
// TODO use _on
.bind( "keydown.menubar", function( event ) {
var menu = $( this );
if ( menu.is( ":hidden" ) ) {
return;
}
switch ( event.keyCode ) {
case $.ui.keyCode.LEFT:
that.previous( event );
event.preventDefault();
break;
case $.ui.keyCode.RIGHT:
that.next( event );
event.preventDefault();
break;
}
});
this.items.each(function() {
var input = $(this),
// TODO menu var is only used on two places, doesn't quite justify the .each
menu = input.next( that.options.menuElement );
// might be a non-menu button
if ( menu.length ) {
// TODO use _on
input.bind( "click.menubar focus.menubar mouseenter.menubar", function( event ) {
// ignore triggered focus event
if ( event.type === "focus" && !event.originalEvent ) {
return;
}
event.preventDefault();
// TODO can we simplify or extractthis check? especially the last two expressions
// there's a similar active[0] == menu[0] check in _open
if ( event.type === "click" && menu.is( ":visible" ) && that.active && that.active[0] === menu[0] ) {
that._close();
return;
}
if ( ( that.open && event.type === "mouseenter" ) || event.type === "click" || that.options.autoExpand ) {
if( that.options.autoExpand ) {
clearTimeout( that.closeTimer );
}
that._open( event, menu );
}
})
// TODO use _on
.bind( "keydown", function( event ) {
switch ( event.keyCode ) {
case $.ui.keyCode.SPACE:
case $.ui.keyCode.UP:
case $.ui.keyCode.DOWN:
that._open( event, $( this ).next() );
event.preventDefault();
break;
case $.ui.keyCode.LEFT:
that.previous( event );
event.preventDefault();
break;
case $.ui.keyCode.RIGHT:
that.next( event );
event.preventDefault();
break;
}
})
.attr( "aria-haspopup", "true" );
// TODO review if these options (menuIcon and buttons) are a good choice, maybe they can be merged
if ( that.options.menuIcon ) {
input.addClass( "ui-state-default" ).append( "<span class='ui-button-icon-secondary ui-icon ui-icon-triangle-1-s'></span>" );
input.removeClass( "ui-button-text-only" ).addClass( "ui-button-text-icon-secondary" );
}
} else {
// TODO use _on
input.bind( "click.menubar mouseenter.menubar", function( event ) {
if ( ( that.open && event.type === "mouseenter" ) || event.type === "click" ) {
that._close();
}
});
}
input
.addClass( "ui-button ui-widget ui-button-text-only ui-menubar-link" )
.attr( "role", "menuitem" )
.wrapInner( "<span class='ui-button-text'></span>" );
if ( that.options.buttons ) {
input.removeClass( "ui-menubar-link" ).addClass( "ui-state-default" );
}
});
that._on( {
keydown: function( event ) {
if ( event.keyCode === $.ui.keyCode.ESCAPE && that.active && that.active.menu( "collapse", event ) !== true ) {
var active = that.active;
that.active.blur();
that._close( event );
active.prev().focus();
}
},
focusin: function( event ) {
clearTimeout( that.closeTimer );
},
focusout: function( event ) {
that.closeTimer = setTimeout( function() {
that._close( event );
}, 150);
},
"mouseleave .ui-menubar-item": function( event ) {
if ( that.options.autoExpand ) {
that.closeTimer = setTimeout( function() {
that._close( event );
}, 150);
}
},
"mouseenter .ui-menubar-item": function( event ) {
clearTimeout( that.closeTimer );
}
});
// Keep track of open submenus
this.openSubmenus = 0;
},
_destroy : function() {
this.menuItems
.removeClass( "ui-menubar-item" )
.removeAttr( "role" );
this.element
.removeClass( "ui-menubar ui-widget-header ui-helper-clearfix" )
.removeAttr( "role" )
.unbind( ".menubar" );
this.items
.unbind( ".menubar" )
.removeClass( "ui-button ui-widget ui-button-text-only ui-menubar-link ui-state-default" )
.removeAttr( "role" )
.removeAttr( "aria-haspopup" )
// TODO unwrap?
.children( "span.ui-button-text" ).each(function( i, e ) {
var item = $( this );
item.parent().html( item.html() );
})
.end()
.children( ".ui-icon" ).remove();
this.element.find( ":ui-menu" )
.menu( "destroy" )
.show()
.removeAttr( "aria-hidden" )
.removeAttr( "aria-expanded" )
.removeAttr( "tabindex" )
.unbind( ".menubar" );
},
_close: function() {
if ( !this.active || !this.active.length ) {
return;
}
this.active
.menu( "collapseAll" )
.hide()
.attr({
"aria-hidden": "true",
"aria-expanded": "false"
});
this.active
.prev()
.removeClass( "ui-state-active" )
.removeAttr( "tabIndex" );
this.active = null;
this.open = false;
this.openSubmenus = 0;
},
_open: function( event, menu ) {
// on a single-button menubar, ignore reopening the same menu
if ( this.active && this.active[0] === menu[0] ) {
return;
}
// TODO refactor, almost the same as _close above, but don't remove tabIndex
if ( this.active ) {
this.active
.menu( "collapseAll" )
.hide()
.attr({
"aria-hidden": "true",
"aria-expanded": "false"
});
this.active
.prev()
.removeClass( "ui-state-active" );
}
// set tabIndex -1 to have the button skipped on shift-tab when menu is open (it gets focus)
var button = menu.prev().addClass( "ui-state-active" ).attr( "tabIndex", -1 );
this.active = menu
.show()
.position( $.extend({
of: button
}, this.options.position ) )
.removeAttr( "aria-hidden" )
.attr( "aria-expanded", "true" )
.menu("focus", event, menu.children( ".ui-menu-item" ).first() )
// TODO need a comment here why both events are triggered
// TODO: heh well given the above comment i'm not sure what the
// implications might be for disabling the focus() call..but it
// messes with text input focus in undesirable ways..so disable it
// we will..until we know why we shouldn't
// .focus()
.focusin();
this.open = true;
},
next: function( event ) {
if ( this.open && this.active.data( "menu" ).active.has( ".ui-menu" ).length ) {
// Track number of open submenus and prevent moving to next menubar item
this.openSubmenus++;
return;
}
this.openSubmenus = 0;
this._move( "next", "first", event );
},
previous: function( event ) {
if ( this.open && this.openSubmenus ) {
// Track number of open submenus and prevent moving to previous menubar item
this.openSubmenus--;
return;
}
this.openSubmenus = 0;
this._move( "prev", "last", event );
},
_move: function( direction, filter, event ) {
var next,
wrapItem;
if ( this.open ) {
next = this.active.closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).first().children( ".ui-menu" ).eq( 0 );
wrapItem = this.menuItems[ filter ]().children( ".ui-menu" ).eq( 0 );
} else {
if ( event ) {
next = $( event.target ).closest( ".ui-menubar-item" )[ direction + "All" ]( this.options.items ).children( ".ui-menubar-link" ).eq( 0 );
wrapItem = this.menuItems[ filter ]().children( ".ui-menubar-link" ).eq( 0 );
} else {
next = wrapItem = this.menuItems.children( "a" ).eq( 0 );
}
}
if ( next.length ) {
if ( this.open ) {
this._open( event, next );
} else {
next.removeAttr( "tabIndex")[0].focus();
}
} else {
if ( this.open ) {
this._open( event, wrapItem );
} else {
wrapItem.removeAttr( "tabIndex")[0].focus();
}
}
}
});
}( jQuery ));

File diff suppressed because it is too large Load diff

View file

@ -1,193 +0,0 @@
/************************************************************
*
* tailbone.edit-shifts.js
*
* Common logic for editing time sheet / schedule data.
*
************************************************************/
var editing_day = null;
var new_shift_id = 1;
function add_shift(focus, uuid, start_time, end_time) {
var shift = $('#snippets .shift').clone();
if (! uuid) {
uuid = 'new-' + (new_shift_id++).toString();
}
shift.attr('data-uuid', uuid);
shift.children('input').each(function() {
var name = $(this).attr('name') + '-' + uuid;
$(this).attr('name', name);
$(this).attr('id', name);
});
shift.children('input[name|="edit_start_time"]').val(start_time || '');
shift.children('input[name|="edit_end_time"]').val(end_time || '');
$('#day-editor .shifts').append(shift);
shift.children('input').timepicker({showPeriod: true});
if (focus) {
shift.children('input:first').focus();
}
}
function calc_minutes(start_time, end_time) {
var start = parseTime(start_time);
start = new Date(2000, 0, 1, start.hh, start.mm);
var end = parseTime(end_time);
end = new Date(2000, 0, 1, end.hh, end.mm);
return Math.floor((end - start) / 1000 / 60);
}
function format_minutes(minutes) {
var hours = Math.floor(minutes / 60);
if (hours) {
minutes -= hours * 60;
}
return hours.toString() + ':' + (minutes < 10 ? '0' : '') + minutes.toString();
}
// stolen from http://stackoverflow.com/a/1788084
function parseTime(s) {
var part = s.match(/(\d+):(\d+)(?: )?(am|pm)?/i);
var hh = parseInt(part[1], 10);
var mm = parseInt(part[2], 10);
var ap = part[3] ? part[3].toUpperCase() : null;
if (ap == 'AM') {
if (hh == 12) {
hh = 0;
}
} else if (ap == 'PM') {
if (hh != 12) {
hh += 12;
}
}
return { hh: hh, mm: mm };
}
function time_input(shift, type) {
var input = shift.children('input[name|="' + type + '_time"]');
if (! input.length) {
input = $('<input type="hidden" name="' + type + '_time-' + shift.data('uuid') + '" />');
shift.append(input);
}
return input;
}
function update_row_hours(row) {
var minutes = 0;
row.find('.day .shift:not(.deleted)').each(function() {
var time_range = $.trim($(this).children('span').text()).split(' - ');
minutes += calc_minutes(time_range[0], time_range[1]);
});
row.children('.total').text(minutes ? format_minutes(minutes) : '0');
}
$(function() {
$('.timesheet').on('click', '.day', function() {
editing_day = $(this);
var editor = $('#day-editor');
var employee = editing_day.siblings('.employee').text();
var date = weekdays[editing_day.get(0).cellIndex - 1];
var shifts = editor.children('.shifts');
shifts.empty();
editing_day.children('.shift:not(.deleted)').each(function() {
var uuid = $(this).data('uuid');
var time_range = $.trim($(this).children('span').text()).split(' - ');
add_shift(false, uuid, time_range[0], time_range[1]);
});
if (! shifts.children('.shift').length) {
add_shift();
}
editor.dialog({
modal: true,
title: employee + ' - ' + date,
position: {my: 'center', at: 'center', of: editing_day},
width: 'auto',
autoResize: true,
buttons: [
{
text: "Update",
click: function() {
// TODO: is this hacky? invoking timepicker to format the time values
// in all cases, to avoid "invalid format" from user input
editor.find('.shifts .shift').each(function() {
var start_time = $(this).children('input[name|="edit_start_time"]');
var end_time = $(this).children('input[name|="edit_end_time"]');
$.timepicker._setTime(start_time.data('timepicker'), start_time.val());
$.timepicker._setTime(end_time.data('timepicker'), end_time.val());
});
// create / update shifts in time table, as needed
editor.find('.shifts .shift').each(function() {
var uuid = $(this).data('uuid');
var start_time = $(this).children('input[name|="edit_start_time"]').val();
var end_time = $(this).children('input[name|="edit_end_time"]').val();
var shift = editing_day.children('.shift[data-uuid="' + uuid + '"]');
if (! shift.length) {
shift = $('<p class="shift" data-uuid="' + uuid + '"><span></span></p>');
shift.append($('<input type="hidden" name="employee_uuid-' + uuid + '" value="'
+ editing_day.parents('tr:first').data('employee-uuid') + '" />'));
editing_day.append(shift);
}
shift.children('span').text(start_time + ' - ' + end_time);
time_input(shift, 'start').val(date + ' ' + start_time);
time_input(shift, 'end').val(date + ' ' + end_time);
});
// remove shifts from time table, as needed
editing_day.children('.shift').each(function() {
var uuid = $(this).data('uuid');
if (! editor.find('.shifts .shift[data-uuid="' + uuid + '"]').length) {
if (uuid.match(/^new-/)) {
$(this).remove();
} else {
$(this).addClass('deleted');
$(this).append($('<input type="hidden" name="delete-' + uuid + '" value="delete" />'));
}
}
});
// mark day as modified, close dialog
editing_day.addClass('modified');
$('.save-changes').button('enable');
$('.undo-changes').button('enable');
update_row_hours(editing_day.parents('tr:first'));
editor.dialog('close');
data_modified = true;
okay_to_leave = false;
}
},
{
text: "Cancel",
click: function() {
editor.dialog('close');
}
}
]
});
});
$('#day-editor #add-shift').click(function() {
add_shift(true);
});
$('#day-editor').on('click', '.shifts button', function() {
$(this).parents('.shift:first').remove();
});
$('.save-changes').click(function() {
$(this).button('disable').button('option', 'label', "Saving Changes...");
okay_to_leave = true;
$('#timetable-form').submit();
});
$('.undo-changes').click(function() {
$(this).button('disable').button('option', 'label', "Refreshing...");
okay_to_leave = true;
location.href = location.href;
});
});

View file

@ -1,58 +1,54 @@
$(function() {
let FeedbackForm = {
props: ['action', 'message'],
template: '#feedback-template',
mixins: [FormPosterMixin],
methods: {
$('#feedback').click(function() {
var dialog = $('#feedback-dialog');
var form = dialog.find('form');
var textarea = form.find('textarea');
dialog.find('.referrer .field').html(location.href);
textarea.val('');
dialog.dialog({
title: "User Feedback",
width: 600,
modal: true,
buttons: [
{
text: "Send",
click: function(event) {
pleaseReplyChanged(value) {
this.$nextTick(() => {
this.$refs.userEmail.focus()
})
},
var msg = $.trim(textarea.val());
if (! msg) {
alert("Please enter a message.");
textarea.select();
textarea.focus();
return;
}
showFeedback() {
this.showDialog = true
this.$nextTick(function() {
this.$refs.textarea.focus()
})
},
disable_button(dialog_button(event));
sendFeedback() {
var data = {
_csrf: form.find('input[name="_csrf"]').val(),
referrer: location.href,
user: form.find('input[name="user"]').val(),
user_name: form.find('input[name="user_name"]').val(),
message: msg
};
let params = {
referrer: this.referrer,
user: this.userUUID,
user_name: this.userName,
please_reply_to: this.pleaseReply ? this.userEmail : null,
message: this.message.trim(),
}
$.ajax(form.attr('action'), {
method: 'POST',
data: data,
success: function(data) {
dialog.dialog('close');
alert("Message successfully sent.\n\nThank you for your feedback.");
}
});
this.submitForm(this.action, params, response => {
}
},
{
text: "Cancel",
click: function() {
dialog.dialog('close');
}
}
]
});
});
this.$buefy.toast.open({
message: "Message sent! Thank you for your feedback.",
type: 'is-info',
duration: 4000, // 4 seconds
})
});
this.showDialog = false
// clear out message, in case they need to send another
this.message = ""
})
},
}
}
let FeedbackFormData = {
referrer: null,
userUUID: null,
userName: null,
pleaseReply: false,
userEmail: null,
showDialog: false,
}

View file

@ -1,386 +0,0 @@
/************************************************************
*
* tailbone.js
*
************************************************************/
/*
* Initialize the disabled filters array. This is populated from within the
* /grids/search.mako template.
*/
var filters_to_disable = [];
/*
* Disables options within the "add filter" dropdown which correspond to those
* filters already being displayed. Called from /grids/search.mako template.
*/
function disable_filter_options() {
while (filters_to_disable.length) {
var filter = filters_to_disable.shift();
var option = $('#add-filter option[value="' + filter + '"]');
option.attr('disabled', 'disabled');
}
}
/*
* Convenience function to disable a UI button.
*/
function disable_button(button, label) {
$(button).button('disable');
if (label === undefined) {
label = $(button).data('working-label') || "Working, please wait...";
}
if (label) {
if (label.slice(-3) != '...') {
label += '...';
}
$(button).button('option', 'label', label);
}
}
function disable_submit_button(form, label) {
// for some reason chrome requires us to do things this way...
// https://stackoverflow.com/questions/16867080/onclick-javascript-stops-form-submit-in-chrome
// https://stackoverflow.com/questions/5691054/disable-submit-button-on-form-submit
var submit = $(form).find('input[type="submit"]');
if (! submit.length) {
submit = $(form).find('button[type="submit"]');
}
if (submit.length) {
disable_button(submit, label);
}
}
/*
* Load next / previous page of results to grid. This function is called on
* the click event from the pager links, via inline script code.
*/
function grid_navigate_page(link, url) {
var wrapper = $(link).parents('div.grid-wrapper');
var grid = wrapper.find('div.grid');
wrapper.mask("Loading...");
$.get(url, function(data) {
wrapper.unmask();
grid.replaceWith(data);
});
}
/*
* Fetch the UUID value associated with a table row.
*/
function get_uuid(obj) {
obj = $(obj);
if (obj.attr('uuid')) {
return obj.attr('uuid');
}
var tr = obj.parents('tr:first');
if (tr.attr('uuid')) {
return tr.attr('uuid');
}
return undefined;
}
/*
* Return a jQuery object containing a button from a dialog. This is a
* convenience function to help with browser differences. It is assumed
* that it is being called from within the relevant button click handler.
* @param {event} event - Click event object.
*/
function dialog_button(event) {
var button = $(event.target);
// TODO: not sure why this workaround is needed for Chrome..?
if (! button.hasClass('ui-button')) {
button = button.parents('.ui-button:first');
}
return button;
}
/**
* Scroll screen as needed to ensure all options are visible, for the given
* select menu widget.
*/
function show_all_options(select) {
if (! select.is(':visible')) {
/*
* Note that the following code was largely stolen from
* http://brianseekford.com/2013/06/03/how-to-scroll-a-container-or-element-into-view-using-jquery-javascript-in-your-html/
*/
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();
var widget = select.selectmenu('menuWidget');
var elemTop = widget.offset().top;
var elemBottom = elemTop + widget.height();
var isScrolled = ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
if (!isScrolled) {
if (widget.height() > $(window).height()) { //then just bring to top of the container
$(window).scrollTop(elemTop)
} else { //try and and bring bottom of container to bottom of screen
$(window).scrollTop(elemTop - ($(window).height() - widget.height()));
}
}
}
}
/*
* reference to existing timeout warning dialog, if any
*/
var session_timeout_warning = null;
/**
* Warn user of impending session timeout.
*/
function timeout_warning() {
if (! session_timeout_warning) {
session_timeout_warning = $('<div id="session-timeout-warning">' +
'You will be logged out in <span class="seconds"></span> ' +
'seconds...</div>');
}
session_timeout_warning.find('.seconds').text('60');
session_timeout_warning.dialog({
title: "Session Timeout Warning",
modal: true,
buttons: {
"Stay Logged In": function() {
session_timeout_warning.dialog('close');
$.get(noop_url, set_timeout_warning_timer);
},
"Logout Now": function() {
location.href = logout_url;
}
}
});
window.setTimeout(timeout_warning_update, 1000);
}
/**
* Decrement the 'seconds' counter for the current timeout warning
*/
function timeout_warning_update() {
if (session_timeout_warning.is(':visible')) {
var span = session_timeout_warning.find('.seconds');
var seconds = parseInt(span.text()) - 1;
if (seconds) {
span.text(seconds.toString());
window.setTimeout(timeout_warning_update, 1000);
} else {
location.href = logout_url;
}
}
}
/**
* Warn user of impending session timeout.
*/
function set_timeout_warning_timer() {
// timout dialog says we're 60 seconds away, but we actually trigger when
// 70 seconds away from supposed timeout, in case of timer drift?
window.setTimeout(timeout_warning, session_timeout * 1000 - 70000);
}
/*
* set initial timer for timeout warning, if applicable
*/
if (session_timeout) {
set_timeout_warning_timer();
}
$(function() {
/*
* enhance buttons
*/
$('button, a.button').button();
$('input[type=submit]').button();
$('input[type=reset]').button();
$('a.button.autodisable').click(function() {
disable_button(this);
});
$('form.autodisable').submit(function() {
disable_submit_button(this);
});
// quickie button
$('#submit-quickie').button('option', 'icons', {primary: 'ui-icon-zoomin'});
/*
* enhance dropdowns
*/
$('select[auto-enhance="true"]').selectmenu();
$('select[auto-enhance="true"]').on('selectmenuopen', function(event, ui) {
show_all_options($(this));
});
/* Also automatically disable any buttons marked for that. */
$('a.button[disabled=disabled]').button('option', 'disabled', true);
/*
* Apply timepicker behavior to text inputs which are marked for it.
*/
$('input[type=text].timepicker').timepicker({
showPeriod: true
});
/*
* When filter labels are clicked, (un)check the associated checkbox.
*/
$('body').on('click', '.grid-wrapper .filter label', function() {
var checkbox = $(this).prev('input[type="checkbox"]');
if (checkbox.prop('checked')) {
checkbox.prop('checked', false);
return false;
}
checkbox.prop('checked', true);
});
/*
* When a new filter is selected in the "add filter" dropdown, show it in
* the UI. This selects the filter's checkbox and puts focus to its input
* element. If all available filters have been displayed, the "add filter"
* dropdown will be hidden.
*/
$('body').on('change', '#add-filter', function() {
var select = $(this);
var filters = select.parents('div.filters:first');
var filter = filters.find('#filter-' + select.val());
var checkbox = filter.find('input[type="checkbox"]:first');
var input = filter.find(':last-child');
checkbox.prop('checked', true);
filter.show();
input.select();
input.focus();
filters.find('input[type="submit"]').show();
filters.find('button[type="reset"]').show();
select.find('option:selected').attr('disabled', true);
select.val('add a filter');
if (select.find('option:enabled').length == 1) {
select.hide();
}
});
/*
* When user clicks the grid filters search button, perform the search in
* the background and reload the grid in-place.
*/
$('body').on('submit', '.filters form', function() {
var form = $(this);
var wrapper = form.parents('div.grid-wrapper');
var grid = wrapper.find('div.grid');
var data = form.serializeArray();
data.push({name: 'partial', value: true});
wrapper.mask("Loading...");
$.get(grid.attr('url'), data, function(data) {
wrapper.unmask();
grid.replaceWith(data);
});
return false;
});
/*
* When user clicks the grid filters reset button, manually clear all
* filter input elements, and submit a new search.
*/
$('body').on('click', '.filters form button[type="reset"]', function() {
var form = $(this).parents('form');
form.find('div.filter').each(function() {
$(this).find('div.value input').val('');
});
form.submit();
return false;
});
$('body').on('click', '.grid thead th.sortable a', function() {
var th = $(this).parent();
var wrapper = th.parents('div.grid-wrapper');
var grid = wrapper.find('div.grid');
var data = {
sort: th.attr('field'),
dir: (th.hasClass('sorted') && th.hasClass('asc')) ? 'desc' : 'asc',
page: 1,
partial: true
};
wrapper.mask("Loading...");
$.get(grid.attr('url'), data, function(data) {
wrapper.unmask();
grid.replaceWith(data);
});
return false;
});
$('body').on('mouseenter', '.grid.hoverable tbody tr', function() {
$(this).addClass('hovering');
});
$('body').on('mouseleave', '.grid.hoverable tbody tr', function() {
$(this).removeClass('hovering');
});
$('body').on('click', '.grid tbody td.view', function() {
var url = $(this).attr('url');
if (url) {
location.href = url;
}
});
$('body').on('click', '.grid tbody td.edit', function() {
var url = $(this).attr('url');
if (url) {
location.href = url;
}
});
$('body').on('click', '.grid tbody td.delete', function() {
var url = $(this).attr('url');
if (url) {
if (confirm("Do you really wish to delete this object?")) {
location.href = url;
}
}
});
// $('div.grid-wrapper').on('change', 'div.grid div.pager select#grid-page-count', function() {
$('body').on('change', '.grid .pager #grid-page-count', function() {
var select = $(this);
var wrapper = select.parents('div.grid-wrapper');
var grid = wrapper.find('div.grid');
var data = {
per_page: select.val(),
partial: true
};
wrapper.mask("Loading...");
$.get(grid.attr('url'), data, function(data) {
wrapper.unmask();
grid.replaceWith(data);
});
});
$('body').on('click', 'div.dialog button.close', function() {
var dialog = $(this).parents('div.dialog:first');
dialog.dialog('close');
});
});

View file

@ -1,267 +0,0 @@
/************************************************************
*
* tailbone.timesheet.edit.js
*
* Common logic for editing time sheet / schedule data.
*
************************************************************/
var editing_day = null;
var new_shift_id = 1;
var show_timepicker = true;
/*
* Add a new shift entry to the editor dialog.
* @param {boolean} focus - Whether to set focus to the start_time input
* element after adding the shift.
* @param {string} uuid - UUID value for the shift, if applicable.
* @param {string} start_time - Value for start_time input element.
* @param {string} end_time - Value for end_time input element.
*/
function add_shift(focus, uuid, start_time, end_time) {
var shift = $('#snippets .shift').clone();
if (! uuid) {
uuid = 'new-' + (new_shift_id++).toString();
}
shift.attr('data-uuid', uuid);
shift.children('input').each(function() {
var name = $(this).attr('name') + '-' + uuid;
$(this).attr('name', name);
$(this).attr('id', name);
});
shift.children('input[name|="edit_start_time"]').val(start_time);
shift.children('input[name|="edit_end_time"]').val(end_time);
$('#day-editor .shifts').append(shift);
// maybe trick timepicker into never showing itself
var args = {showPeriod: true};
if (! show_timepicker) {
args.showOn = 'button';
args.button = '#nevershow';
}
shift.children('input').timepicker(args);
if (focus) {
shift.children('input:first').focus();
}
}
/**
* Calculate the number of minutes between given the times.
* @param {string} start_time - Value from start_time input element.
* @param {string} end_time - Value from end_time input element.
*/
function calc_minutes(start_time, end_time) {
var start = parseTime(start_time);
var end = parseTime(end_time);
if (start && end) {
start = new Date(2000, 0, 1, start.hh, start.mm);
end = new Date(2000, 0, 1, end.hh, end.mm);
return Math.floor((end - start) / 1000 / 60);
}
}
/**
* Converts a number of minutes into string of HH:MM format.
* @param {number} minutes - Number of minutes to be converted.
*/
function format_minutes(minutes) {
var hours = Math.floor(minutes / 60);
if (hours) {
minutes -= hours * 60;
}
return hours.toString() + ':' + (minutes < 10 ? '0' : '') + minutes.toString();
}
/**
* NOTE: most of this logic was stolen from http://stackoverflow.com/a/1788084
*
* Parse a time string and convert to simple object with hh and mm keys.
* @param {string} time - Time value in 'HH:MM PP' format, or close enough.
*/
function parseTime(time) {
if (time) {
var part = time.match(/(\d+):(\d+)(?: )?(am|pm)?/i);
if (part) {
var hh = parseInt(part[1], 10);
var mm = parseInt(part[2], 10);
var ap = part[3] ? part[3].toUpperCase() : null;
if (ap == 'AM') {
if (hh == 12) {
hh = 0;
}
} else if (ap == 'PM') {
if (hh != 12) {
hh += 12;
}
}
return { hh: hh, mm: mm };
}
}
}
/**
* Return a jQuery object containing the hidden start or end time input element
* for the shift (i.e. within the *main* timesheet form). This will create the
* input if necessary.
* @param {jQuery} shift - A jQuery object for the shift itself.
* @param {string} type - Should be 'start' or 'end' only.
*/
function time_input(shift, type) {
var input = shift.children('input[name|="' + type + '_time"]');
if (! input.length) {
input = $('<input type="hidden" name="' + type + '_time-' + shift.data('uuid') + '" />');
shift.append(input);
}
return input;
}
/**
* Update the weekly hour total for a given row (employee).
* @param {jQuery} row - A jQuery object for the row to be updated.
*/
function update_row_hours(row) {
var minutes = 0;
row.find('.day .shift:not(.deleted)').each(function() {
var time_range = $.trim($(this).children('span').text()).split(' - ');
minutes += calc_minutes(time_range[0], time_range[1]);
});
row.children('.total').text(minutes ? format_minutes(minutes) : '0');
}
/**
* Clean up user input within the editor dialog, e.g. '8:30am' => '08:30 AM'.
* This also should ensure invalid input will become empty string.
*/
function cleanup_editor_input() {
// TODO: is this hacky? invoking timepicker to format the time values
// in all cases, to avoid "invalid format" from user input
var backward = false;
$('#day-editor .shifts .shift').each(function() {
var start_time = $(this).children('input[name|="edit_start_time"]');
var end_time = $(this).children('input[name|="edit_end_time"]');
$.timepicker._setTime(start_time.data('timepicker'), start_time.val() || '??');
$.timepicker._setTime(end_time.data('timepicker'), end_time.val() || '??');
var t_start = parseTime(start_time.val());
var t_end = parseTime(end_time.val());
if (t_start && t_end) {
if ((t_start.hh > t_end.hh) || ((t_start.hh == t_end.hh) && (t_start.mm > t_end.mm))) {
alert("Start time falls *after* end time! Please fix...");
start_time.focus().select();
backward = true;
return false;
}
}
});
return !backward;
}
/**
* Update the main timesheet table based on editor dialog input. This updates
* both the displayed timesheet, as well as any hidden input elements on the
* main form.
*/
function update_timetable() {
var date = weekdays[editing_day.get(0).cellIndex - 1];
// add or update
$('#day-editor .shifts .shift').each(function() {
var uuid = $(this).data('uuid');
var start_time = $(this).children('input[name|="edit_start_time"]').val();
var end_time = $(this).children('input[name|="edit_end_time"]').val();
var shift = editing_day.children('.shift[data-uuid="' + uuid + '"]');
if (! shift.length) {
if (! (start_time || end_time)) {
return;
}
shift = $('<p class="shift" data-uuid="' + uuid + '"><span></span></p>');
shift.append($('<input type="hidden" name="employee_uuid-' + uuid + '" value="'
+ editing_day.parents('tr:first').data('employee-uuid') + '" />'));
editing_day.append(shift);
}
shift.children('span').text((start_time || '??') + ' - ' + (end_time || '??'));
start_time = start_time ? (date + ' ' + start_time) : '';
end_time = end_time ? (date + ' ' + end_time) : '';
time_input(shift, 'start').val(start_time);
time_input(shift, 'end').val(end_time);
});
// remove / mark for deletion
editing_day.children('.shift').each(function() {
var uuid = $(this).data('uuid');
if (! $('#day-editor .shifts .shift[data-uuid="' + uuid + '"]').length) {
if (uuid.match(/^new-/)) {
$(this).remove();
} else {
$(this).addClass('deleted');
$(this).append($('<input type="hidden" name="delete-' + uuid + '" value="delete" />'));
}
}
});
}
/**
* Perform full "save" action for time sheet form, direct from day editor dialog.
*/
function save_dialog() {
if (! cleanup_editor_input()) {
return false;
}
var save = $('#day-editor').parents('.ui-dialog').find('.ui-dialog-buttonpane button:first');
save.button('disable').button('option', 'label', "Saving...");
update_timetable();
$('#timetable-form').submit();
return true;
}
/*
* on document load...
*/
$(function() {
/*
* Within editor dialog, clicking Add Shift button will create a new/empty
* shift and set focus to its start_time input.
*/
$('#day-editor #add-shift').click(function() {
add_shift(true);
});
/*
* Within editor dialog, clicking a shift's "trash can" button will remove
* the shift.
*/
$('#day-editor').on('click', '.shifts button', function() {
$(this).parents('.shift:first').remove();
});
/*
* Within editor dialog, Enter press within time field "might" trigger
* save. Note that this is only done for timesheet editing, not schedule.
*/
$('#day-editor').on('keydown', '.shifts input[type="text"]', function(event) {
if (!show_timepicker) { // TODO: this implies too much, should be cleaner
if (event.which == 13) {
save_dialog();
return false;
}
}
});
});

View file

@ -1,14 +0,0 @@
/******************************
* tweaks for root user
******************************/
.navbar .navbar-end .navbar-link.root-user,
.navbar .navbar-end .navbar-link.root-user:hover,
.navbar .navbar-end .navbar-link.root-user.is_active,
.navbar .navbar-end .navbar-item.root-user,
.navbar .navbar-end .navbar-item.root-user:hover,
.navbar .navbar-end .navbar-item.root-user.is_active {
background-color: red;
font-weight: bold;
}

View file

@ -1,22 +0,0 @@
/******************************
* Grid Filters
******************************/
.filters .filter {
margin-bottom: 0.5rem;
}
.filters .filter-fieldname .field,
.filters .filter-fieldname .field label {
width: 100%;
}
.filters .filter-fieldname .field label {
justify-content: left;
}
.filters .filter-verb .select,
.filters .filter-verb .select select {
width: 100%;
}

View file

@ -1,61 +0,0 @@
/******************************
* forms
******************************/
/* note that this should only apply to "normal" primary forms */
/* TODO: replace this with bulma equivalent */
.form {
padding-left: 5em;
}
/* note that this should only apply to "normal" primary forms */
.form-wrapper .form .field.is-horizontal .field-label .label {
text-align: left;
white-space: nowrap;
width: 18em;
}
/* note that this should only apply to "normal" primary forms */
.form-wrapper .form .field.is-horizontal .field-body {
min-width: 30em;
}
/* note that this should only apply to "normal" primary forms */
.form-wrapper .form .field.is-horizontal .field-body .select,
.form-wrapper .form .field.is-horizontal .field-body .select select {
width: 100%;
}
/******************************
* field-wrappers
******************************/
/* TODO: replace this with bulma equivalent */
.field-wrapper {
clear: both;
min-height: 30px;
overflow: auto;
margin: 15px;
}
/* TODO: replace this with bulma equivalent */
.field-wrapper .field-row {
display: table-row;
}
/* TODO: replace this with bulma equivalent */
.field-wrapper label {
display: table-cell;
vertical-align: top;
width: 18em;
font-weight: bold;
padding-top: 2px;
white-space: nowrap;
}
/* TODO: replace this with bulma equivalent */
.field-wrapper .field {
display: table-cell;
line-height: 25px;
}

View file

@ -1,15 +0,0 @@
/********************************************************************************
* grids.css
*
* Style tweaks for the Buefy grids.
********************************************************************************/
/******************************
* actions column
******************************/
a.grid-action {
white-space: nowrap;
}

View file

@ -1,150 +0,0 @@
/******************************
* main layout
******************************/
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.content-wrapper {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-between;
}
/******************************
* header
******************************/
/* this is the one in the very top left of screen, next to logo and linked to
the home page */
#global-header-title {
margin-left: 0.3rem;
}
header .level {
/* TODO: not sure what this 60px was supposed to do? but it broke the */
/* styles for the feedback dialog, so disabled it is.
/* height: 60px; */
/* line-height: 60px; */
padding-left: 0.5em;
padding-right: 0.5em;
}
header .level #header-logo {
display: inline-block;
}
header .level .global-title,
header .level-left .global-title {
font-size: 2em;
font-weight: bold;
}
/* indent nested menu items a bit */
header .navbar-item.nested {
padding-left: 2.5rem;
}
header span.header-text {
font-size: 2em;
font-weight: bold;
margin-right: 10px;
}
header .level .theme-picker {
display: inline-flex;
}
#content-title {
padding: 0.3rem;
}
#content-title h1 {
font-size: 2rem;
margin-left: 1rem;
}
/******************************
* content
******************************/
#page-body {
padding: 0.4em;
}
/******************************
* context menu
******************************/
#context-menu {
margin-bottom: 1em;
margin-left: 1em;
text-align: right;
white-space: nowrap;
}
/******************************
* "object helper" panel
******************************/
.object-helpers .panel-heading {
white-space: nowrap;
}
.object-helpers a {
white-space: nowrap;
}
.object-helper {
border: 1px solid black;
margin: 1em;
padding: 1em;
width: 20em;
}
.object-helper-content {
margin-top: 1em;
}
/******************************
* markdown
******************************/
.rendered-markdown p,
.rendered-markdown ul {
margin-bottom: 1rem;
}
.rendered-markdown .codehilite {
margin-bottom: 2rem;
}
/******************************
* fix datepicker within modals
* TODO: someday this may not be necessary? cf.
* https://github.com/buefy/buefy/issues/292#issuecomment-347365637
******************************/
.modal .animation-content .modal-card {
overflow: visible !important;
}
.modal-card-body {
overflow: visible !important;
}
/******************************
* feedback
******************************/
.feedback-dialog .red {
color: red;
font-weight: bold;
}

View file

@ -1,54 +0,0 @@
let FeedbackForm = {
props: ['action', 'message'],
template: '#feedback-template',
mixins: [FormPosterMixin],
methods: {
pleaseReplyChanged(value) {
this.$nextTick(() => {
this.$refs.userEmail.focus()
})
},
showFeedback() {
this.showDialog = true
this.$nextTick(function() {
this.$refs.textarea.focus()
})
},
sendFeedback() {
let params = {
referrer: this.referrer,
user: this.userUUID,
user_name: this.userName,
please_reply_to: this.pleaseReply ? this.userEmail : null,
message: this.message.trim(),
}
this.submitForm(this.action, params, response => {
this.$buefy.toast.open({
message: "Message sent! Thank you for your feedback.",
type: 'is-info',
duration: 4000, // 4 seconds
})
this.showDialog = false
// clear out message, in case they need to send another
this.message = ""
})
},
}
}
let FeedbackFormData = {
referrer: null,
userUUID: null,
userName: null,
pleaseReply: false,
userEmail: null,
showDialog: false,
}

View file

@ -1,63 +1,5 @@
## -*- coding: utf-8; -*-
## TODO: This function signature is getting out of hand...
<%def name="autocomplete(field_name, service_url, field_value=None, field_display=None, width='300px', select=None, selected=None, cleared=None, change_clicked=None, options={})">
<div id="${field_name}-container" class="autocomplete-container">
${h.hidden(field_name, id=field_name, value=field_value)}
${h.text(field_name+'-textbox', id=field_name+'-textbox', value=field_display,
class_='autocomplete-textbox', style='display: none;' if field_value else '')}
<div id="${field_name}-display" class="autocomplete-display"${'' if field_value else ' style="display: none;"'|n}>
<span>${field_display or ''}</span>
<button type="button" id="${field_name}-change" class="autocomplete-change">Change</button>
</div>
</div>
<script type="text/javascript">
$(function() {
$('#${field_name}-textbox').autocomplete({
source: '${service_url}',
autoFocus: true,
% for key, value in options.items():
${key}: ${value},
% endfor
focus: function(event, ui) {
return false;
},
% if select:
select: ${select}
% else:
select: function(event, ui) {
$('#${field_name}').val(ui.item.value);
$('#${field_name}-display span:first').text(ui.item.label);
$('#${field_name}-textbox').hide();
$('#${field_name}-display').show();
% if selected:
${selected}(ui.item.value, ui.item.label);
% endif
return false;
}
% endif
});
$('#${field_name}-change').click(function() {
% if change_clicked:
if (! ${change_clicked}()) {
return false;
}
% endif
$('#${field_name}').val('');
$('#${field_name}-display').hide();
with ($('#${field_name}-textbox')) {
val('');
show();
focus();
}
% if cleared:
${cleared}();
% endif
});
});
</script>
</%def>
<%def name="tailbone_autocomplete_template()">
<script type="text/x-template" id="tailbone-autocomplete-template">
<div>

View file

@ -73,7 +73,6 @@
</%def>
<%def name="core_javascript()">
${self.jquery()}
${self.vuejs()}
${self.buefy()}
${self.fontawesome()}
@ -92,22 +91,32 @@
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.grid.js') + '?ver={}'.format(tailbone.__version__))}
<script type="text/javascript">
var session_timeout = ${request.get_session_timeout() or 'null'};
var logout_url = '${request.route_url('logout')}';
var noop_url = '${request.route_url('noop')}';
$(function() {
## NOTE: this code was copied from
## https://bulma.io/documentation/components/navbar/#navbar-menu
$('.navbar-burger').click(function() {
$('.navbar-burger').toggleClass('is-active');
$('.navbar-menu').toggleClass('is-active');
});
});
</script>
</%def>
<%def name="jquery()">
${h.javascript_link(h.get_liburl(request, 'jquery'))}
## NOTE: this code was copied from
## https://bulma.io/documentation/components/navbar/#navbar-menu
document.addEventListener('DOMContentLoaded', () => {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0)
// Add a click event on each of them
$navbarBurgers.forEach( el => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target
const $target = document.getElementById(target)
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active')
$target.classList.toggle('is-active')
})
})
})
</script>
</%def>
<%def name="vuejs()">
@ -129,14 +138,12 @@
${self.buefy_styles()}
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/base.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/layout.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/base.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/layout.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/grids.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/grids.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/grids.rowstatus.css') + '?ver={}'.format(tailbone.__version__))}
## ${h.stylesheet_link(request.static_url('tailbone:static/css/filters.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/filters.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/forms.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/grids.rowstatus.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/filters.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/forms.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/diffs.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/codehilite.css') + '?ver={}'.format(tailbone.__version__))}
@ -162,12 +169,6 @@
% endif
</%def>
## TODO: this is only being referenced by the progress template i think?
## (so, should make a Buefy progress page at least)
<%def name="jquery_theme()">
${h.stylesheet_link(h.get_liburl(request, 'jquery_ui'))}
</%def>
<%def name="extra_styles()"></%def>
<%def name="head_tags()"></%def>
@ -201,14 +202,14 @@
@select="globalSearchSelect">
</b-autocomplete>
</div>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false">
<a role="button" class="navbar-burger" data-target="navbar-menu" aria-label="menu" aria-expanded="false">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div class="navbar-menu">
<div class="navbar-menu" id="navbar-menu">
<div class="navbar-start">
<div v-if="globalSearchData.length"
@ -742,7 +743,7 @@
<%def name="declare_whole_page_vars()">
${page_help.declare_vars()}
${multi_file_upload.declare_vars()}
${h.javascript_link(request.static_url('tailbone:static/themes/falafel/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}
<script type="text/javascript">
let WholePage = {

View file

@ -2,23 +2,6 @@
<%inherit file="/master/view.mako" />
<%namespace file="/util.mako" import="view_profiles_helper" />
<%def name="extra_javascript()">
${parent.extra_javascript()}
% if master.people_detachable and request.has_perm('{}.detach_person'.format(permission_prefix)):
<script type="text/javascript">
$(function() {
$('.people .grid .actions a.detach').click(function() {
if (! confirm("Are you sure you wish to detach this Person from the Customer?")) {
return false;
}
});
});
</script>
% endif
</%def>
<%def name="object_helpers()">
${parent.object_helpers()}
% if show_profiles_helper and instance.people:

View file

@ -1,8 +1,6 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/view.mako" />
## TODO: this page still uses jQuery but should use Vue.js
<%def name="extra_styles()">
${parent.extra_styles()}
<style type="text/css">

View file

@ -1,99 +0,0 @@
## -*- coding: utf-8; -*-
% if not readonly:
<% _focus_rendered = False %>
${h.form(form.action_url, id=dform.formid, method='post', enctype='multipart/form-data', **form_kwargs)}
${h.csrf_token(request)}
% endif
% if dform.error:
<div class="error-messages">
<div class="ui-state-error ui-corner-all">
<span style="float: left; margin-right: .3em;" class="ui-icon ui-icon-alert"></span>
Please see errors below.
</div>
<div class="ui-state-error ui-corner-all">
<span style="float: left; margin-right: .3em;" class="ui-icon ui-icon-alert"></span>
${dform.error}
</div>
</div>
% endif
% for field in form.fields:
## % if readonly or field.name in readonly_fields:
% if readonly:
${render_field_readonly(field)|n}
% elif field not in dform and field in form.readonly_fields:
${render_field_readonly(field)|n}
% elif field in dform:
<% field = dform[field] %>
% if form.field_visible(field.name):
<div class="field-wrapper ${field.name} ${'with-error' if field.error else ''}">
% if field.error:
<div class="field-error">
% for msg in field.error.messages():
<span class="error-msg">${msg}</span>
% endfor
</div>
% endif
<div class="field-row">
<label for="${field.oid}">${form.get_label(field.name)}</label>
<div class="field">
${field.serialize()|n}
</div>
</div>
% if form.has_helptext(field.name):
<span class="instructions">${form.render_helptext(field.name)}</span>
% endif
</div>
## % if not _focus_rendered and (fieldset.focus is True or fieldset.focus is field):
% if not readonly and not _focus_rendered:
## % if not field.is_readonly() and getattr(field.renderer, 'needs_focus', True):
% if not field.widget.readonly:
<script type="text/javascript">
$(function() {
## % if hasattr(field.renderer, 'focus_name'):
## $('#${field.renderer.focus_name}').focus();
## % else:
## $('#${field.renderer.name}').focus();
## % endif
$('#${field.oid}').focus();
});
</script>
<% _focus_rendered = True %>
% endif
% endif
% else:
## hidden field
${field.serialize()|n}
% endif
% endif
% endfor
% if buttons:
${buttons|n}
% elif not readonly and (buttons is Undefined or (buttons is not None and buttons is not False)):
<div class="buttons">
## ${h.submit('create', form.create_label if form.creating else form.update_label)}
${h.submit('save', getattr(form, 'submit_label', getattr(form, 'save_label', "Submit")), class_='button is-primary')}
## % if form.creating and form.allow_successive_creates:
## ${h.submit('create_and_continue', form.successive_create_label)}
## % endif
% if getattr(form, 'show_reset', False):
<input type="reset" value="Reset" class="button" />
% endif
% if getattr(form, 'show_cancel', True):
${h.link_to("Cancel", form.cancel_url, class_='cancel button{}'.format(' autodisable' if form.auto_disable_cancel else ''))}
% endif
</div>
% endif
% if not readonly:
${h.end_form()}
% endif

View file

@ -1,8 +0,0 @@
## -*- coding: utf-8; -*-
<div class="form">
${form.render_deform(readonly=True)|n}
% if buttons:
${buttons|n}
% endif
</div><!-- form -->

View file

@ -1,37 +0,0 @@
## -*- coding: utf-8 -*-
<div class="filters" url="${search.request.current_route_url()}">
${search.begin()}
${search.hidden('filters', 'true')}
<% visible = [] %>
% for f in search.sorted_filters():
<div class="filter" id="filter-${f.name}"${' style="display: none;"' if not search.config.get('include_filter_'+f.name) else ''|n}>
${search.checkbox('include_filter_'+f.name)}
<label for="${f.name}">${f.label}</label>
${f.types_select()}
<div class="value">
${f.value_control()}
</div>
</div>
% if search.config.get('include_filter_'+f.name):
<% visible.append(f.name) %>
% endif
% endfor
<div class="buttons">
${search.add_filter(visible)}
${search.submit('submit', "Search", style='display: none;' if not visible else None)}
<button type="reset"${' style="display: none;"' if not visible else ''|n}>Reset</button>
</div>
${search.end()}
% if visible:
<script language="javascript" type="text/javascript">
filters_to_disable = [
% for field in visible:
'${field}',
% endfor
];
$(function() {
disable_filter_options();
});
</script>
% endif
</div>

View file

@ -6,19 +6,8 @@
## ##############################################################################
<%inherit file="/page.mako" />
## TODO: this page still uses old-style grid but should use Buefy grid
<%def name="title()">${model_title_plural} » ${instance_title} » history</%def>
<%def name="extra_javascript()">
${parent.extra_javascript()}
<script type="text/javascript">
$(function() {
$('.grid-wrapper').gridwrapper();
});
</script>
</%def>
<%def name="content_title()">
Version History
</%def>

View file

@ -1,402 +0,0 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/view.mako" />
<%def name="extra_javascript()">
${parent.extra_javascript()}
<script type="text/javascript">
## NOTE: we must delay activation of accordions, otherwise they do not
## seem to "resize" correctly
var customer_accordion_activated = false;
var user_accordion_activated = false;
$(function() {
$('#profile-tabs').tabs({
activate: function(event, ui) {
## activate accordion, first time tab is activated
if (ui.newPanel.selector == '#customer-tab') {
if (! customer_accordion_activated) {
$('#customers-accordion').accordion();
customer_accordion_activated = true;
}
} else if (ui.newPanel.selector == '#user-tab') {
if (! user_accordion_activated) {
$('#users-accordion').accordion();
user_accordion_activated = true;
}
}
}
});
});
</script>
</%def>
<div id="profile-tabs">
<ul>
<li><a href="#personal-tab">Personal</a></li>
<li><a href="#customer-tab">Customer</a></li>
<li><a href="#employee-tab">Employee</a></li>
<li><a href="#user-tab">User</a></li>
</ul>
<div id="personal-tab">
<div style="display: flex; justify-content: space-between;">
<div>
<div class="field-wrapper first_name">
<div class="field-row">
<label>First Name</label>
<div class="field">
${person.first_name}
</div>
</div>
</div>
<div class="field-wrapper middle_name">
<div class="field-row">
<label>Middle Name</label>
<div class="field">
${person.middle_name}
</div>
</div>
</div>
<div class="field-wrapper last_name">
<div class="field-row">
<label>Last Name</label>
<div class="field">
${person.last_name}
</div>
</div>
</div>
<div class="field-wrapper street">
<div class="field-row">
<label>Street 1</label>
<div class="field">
${person.address.street if person.address else ''}
</div>
</div>
</div>
<div class="field-wrapper street2">
<div class="field-row">
<label>Street 2</label>
<div class="field">
${person.address.street2 if person.address else ''}
</div>
</div>
</div>
<div class="field-wrapper city">
<div class="field-row">
<label>City</label>
<div class="field">
${person.address.city if person.address else ''}
</div>
</div>
</div>
<div class="field-wrapper state">
<div class="field-row">
<label>State</label>
<div class="field">
${person.address.state if person.address else ''}
</div>
</div>
</div>
<div class="field-wrapper zipcode">
<div class="field-row">
<label>Zipcode</label>
<div class="field">
${person.address.zipcode if person.address else ''}
</div>
</div>
</div>
% if person.phones:
% for phone in person.phones:
<div class="field-wrapper">
<div class="field-row">
<label>Phone Number</label>
<div class="field">
${phone.number} (type: ${phone.type})
</div>
</div>
</div>
% endfor
% else:
<div class="field-wrapper">
<div class="field-row">
<label>Phone Number</label>
<div class="field">
(none on file)
</div>
</div>
</div>
% endif
% if person.emails:
% for email in person.emails:
<div class="field-wrapper">
<div class="field-row">
<label>Email Address</label>
<div class="field">
${email.address} (type: ${email.type})
</div>
</div>
</div>
% endfor
% else:
<div class="field-wrapper">
<div class="field-row">
<label>Email Address</label>
<div class="field">
(none on file)
</div>
</div>
</div>
% endif
</div>
<div>
% if request.has_perm('people.view'):
${h.link_to("View Person", url('people.view', uuid=person.uuid), class_='button')}
% endif
</div>
</div>
</div><!-- personal-tab -->
<div id="customer-tab">
% if person.customers:
<p>${person} is associated with ${len(person.customers)} customer account(s)</p>
<br />
<div id="customers-accordion">
% for customer in person.customers:
<h3>${customer.id} - ${customer.name}</h3>
<div>
<div style="display: flex; justify-content: space-between;">
<div>
<div class="field-wrapper id">
<div class="field-row">
<label>ID</label>
<div class="field">
${customer.id or ''}
</div>
</div>
</div>
<div class="field-wrapper name">
<div class="field-row">
<label>Name</label>
<div class="field">
${customer.name}
</div>
</div>
</div>
% if customer.phones:
% for phone in customer.phones:
<div class="field-wrapper">
<div class="field-row">
<label>Phone Number</label>
<div class="field">
${phone.number} (type: ${phone.type})
</div>
</div>
</div>
% endfor
% else:
<div class="field-wrapper">
<div class="field-row">
<label>Phone Number</label>
<div class="field">
(none on file)
</div>
</div>
</div>
% endif
% if customer.emails:
% for email in customer.emails:
<div class="field-wrapper">
<div class="field-row">
<label>Email Address</label>
<div class="field">
${email.address} (type: ${email.type})
</div>
</div>
</div>
% endfor
% else:
<div class="field-wrapper">
<div class="field-row">
<label>Email Address</label>
<div class="field">
(none on file)
</div>
</div>
</div>
% endif
</div>
<div>
% if request.has_perm('customers.view'):
${h.link_to("View Customer", url('customers.view', uuid=customer.uuid), class_='button')}
% endif
</div>
</div>
</div>
% endfor
</div>
% else:
<p>${person} has never been a customer.</p>
% endif
</div><!-- customer-tab -->
<div id="employee-tab">
% if employee:
<div style="display: flex; justify-content: space-between;">
<div>
<div class="field-wrapper id">
<div class="field-row">
<label>ID</label>
<div class="field">
${employee.id or ''}
</div>
</div>
</div>
<div class="field-wrapper display_name">
<div class="field-row">
<label>Display Name</label>
<div class="field">
${employee.display_name or ''}
</div>
</div>
</div>
<div class="field-wrapper status">
<div class="field-row">
<label>Status</label>
<div class="field">
${enum.EMPLOYEE_STATUS.get(employee.status, '')}
</div>
</div>
</div>
% if employee.phones:
% for phone in employee.phones:
<div class="field-wrapper">
<div class="field-row">
<label>Phone Number</label>
<div class="field">
${phone.number} (type: ${phone.type})
</div>
</div>
</div>
% endfor
% else:
<div class="field-wrapper">
<div class="field-row">
<label>Phone Number</label>
<div class="field">
(none on file)
</div>
</div>
</div>
% endif
% if employee.emails:
% for email in employee.emails:
<div class="field-wrapper">
<div class="field-row">
<label>Email Address</label>
<div class="field">
${email.address} (type: ${email.type})
</div>
</div>
</div>
% endfor
% else:
<div class="field-wrapper">
<div class="field-row">
<label>Email Address</label>
<div class="field">
(none on file)
</div>
</div>
</div>
% endif
</div>
<div>
% if request.has_perm('employees.view'):
${h.link_to("View Employee", url('employees.view', uuid=employee.uuid), class_='button')}
% endif
</div>
</div>
% else:
<p>${person} has never been an employee.</p>
% endif
</div><!-- employee-tab -->
<div id="user-tab">
% if person.users:
<p>${person} is associated with ${len(person.users)} user account(s)</p>
<br />
<div id="users-accordion">
% for user in person.users:
<h3>${user.username}</h3>
<div>
<div style="display: flex; justify-content: space-between;">
<div>
<div class="field-wrapper id">
<div class="field-row">
<label>Username</label>
<div class="field">
${user.username}
</div>
</div>
</div>
</div>
<div>
% if request.has_perm('users.view'):
${h.link_to("View User", url('users.view', uuid=user.uuid), class_='button')}
% endif
</div>
</div>
</div>
% endfor
</div>
% else:
<p>${person} has never been a user.</p>
% endif
</div><!-- user-tab -->
</div><!-- profile-tabs -->

View file

@ -1,99 +1,6 @@
## -*- coding: utf-8; -*-
<%inherit file="/batch/create.mako" />
<%def name="extra_javascript()">
${parent.extra_javascript()}
${self.func_show_mode()}
<script type="text/javascript">
var purchases_field = '${purchases_field}';
var purchases = null; // TODO: where is this used?
function vendor_selected(uuid, name) {
var mode = $('.mode select').val();
if (mode == ${enum.PURCHASE_BATCH_MODE_RECEIVING} || mode == ${enum.PURCHASE_BATCH_MODE_COSTING}) {
var purchases = $('.purchase_uuid select');
purchases.empty();
var data = {'vendor_uuid': uuid, 'mode': mode};
$.get('${url('purchases.batch.eligible_purchases')}', data, function(data) {
if (data.error) {
alert(data.error);
} else {
$.each(data.purchases, function(i, purchase) {
purchases.append($('<option value="' + purchase.key + '">' + purchase.display + '</option>'));
});
}
});
// TODO: apparently refresh doesn't work right?
// http://stackoverflow.com/a/10280078
// purchases.selectmenu('refresh');
purchases.selectmenu('destroy').selectmenu();
}
}
function vendor_cleared() {
var purchases = $('.purchase_uuid select');
purchases.empty();
// TODO: apparently refresh doesn't work right?
// http://stackoverflow.com/a/10280078
// purchases.selectmenu('refresh');
purchases.selectmenu('destroy').selectmenu();
}
$(function() {
$('.field-wrapper.mode select').selectmenu({
change: function(event, ui) {
show_mode(ui.item.value);
}
});
show_mode(${batch.mode or enum.PURCHASE_BATCH_MODE_ORDERING});
});
</script>
</%def>
<%def name="func_show_mode()">
<script type="text/javascript">
function show_mode(mode) {
if (mode == ${enum.PURCHASE_BATCH_MODE_ORDERING}) {
$('.field-wrapper.store_uuid').show();
$('.field-wrapper.' + purchases_field).hide();
$('.field-wrapper.department_uuid').show();
$('.field-wrapper.buyer_uuid').show();
$('.field-wrapper.date_ordered').show();
$('.field-wrapper.date_received').hide();
$('.field-wrapper.po_number').show();
$('.field-wrapper.invoice_date').hide();
$('.field-wrapper.invoice_number').hide();
} else if (mode == ${enum.PURCHASE_BATCH_MODE_RECEIVING}) {
$('.field-wrapper.store_uuid').hide();
$('.field-wrapper.purchase_uuid').show();
$('.field-wrapper.department_uuid').hide();
$('.field-wrapper.buyer_uuid').hide();
$('.field-wrapper.date_ordered').hide();
$('.field-wrapper.date_received').show();
$('.field-wrapper.invoice_date').show();
$('.field-wrapper.invoice_number').show();
} else if (mode == ${enum.PURCHASE_BATCH_MODE_COSTING}) {
$('.field-wrapper.store_uuid').hide();
$('.field-wrapper.purchase_uuid').show();
$('.field-wrapper.department_uuid').hide();
$('.field-wrapper.buyer_uuid').hide();
$('.field-wrapper.date_ordered').hide();
$('.field-wrapper.date_received').hide();
$('.field-wrapper.invoice_date').show();
$('.field-wrapper.invoice_number').show();
}
}
</script>
</%def>
## TODO: deprecate / remove this
${parent.body()}

View file

@ -1,97 +1,85 @@
## -*- coding: utf-8; -*-
<%inherit file="/master/index.mako" />
<%def name="extra_javascript()">
${parent.extra_javascript()}
<%def name="grid_tools()">
${parent.grid_tools()}
<b-button type="is-primary"
@click="changeStatusInit()"
:disabled="!selected_uuids.length">
Change Status
</b-button>
<b-modal has-modal-card
:active.sync="changeStatusShowDialog">
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Change Status</p>
</header>
<section class="modal-card-body">
<p class="block">
Please choose the appropriate status for the selected credits.
</p>
<b-field label="Status">
<b-select v-model="changeStatusValue">
<option v-for="status in changeStatusOptions"
:key="status.value"
:value="status.value">
{{ status.label }}
</option>
</b-select>
</b-field>
</section>
<footer class="modal-card-foot">
<b-button @click="changeStatusShowDialog = false">
Cancel
</b-button>
<b-button type="is-primary"
@click="changeStatusSubmit()"
:disabled="changeStatusSubmitting || !changeStatusValue"
icon-pack="fas"
icon-left="save">
{{ changeStatusSubmitting ? "Working, please wait..." : "Save" }}
</b-button>
</footer>
</div>
</b-modal>
${h.form(url('purchases.credits.change_status'), ref='changeStatusForm')}
${h.csrf_token(request)}
${h.hidden('uuids', **{':value': 'selected_uuids'})}
${h.hidden('status', **{':value': 'changeStatusValue'})}
${h.end_form()}
</%def>
<%def name="modify_this_page_vars()">
${parent.modify_this_page_vars()}
<script type="text/javascript">
function update_change_status_button() {
var count = $('.grid tr:not(.header) td.checkbox input:checked').length;
$('button.change-status').button('option', 'disabled', count < 1);
${grid.component_studly}Data.changeStatusShowDialog = false
${grid.component_studly}Data.changeStatusOptions = ${json.dumps(status_options)|n}
${grid.component_studly}Data.changeStatusValue = null
${grid.component_studly}Data.changeStatusSubmitting = false
${grid.component_studly}.methods.changeStatusInit = function() {
this.changeStatusValue = null
this.changeStatusShowDialog = true
}
$(function() {
${grid.component_studly}.methods.changeStatusSubmit = function() {
this.changeStatusSubmitting = true
this.$refs.changeStatusForm.submit()
}
$('.grid-wrapper').on('click', 'tr.header td.checkbox input', function() {
update_change_status_button();
});
$('.grid-wrapper').on('click', '.grid tr:not(.header) td.checkbox input', function() {
update_change_status_button();
});
$('.grid-wrapper').on('click', '.grid tr:not(.header)', function() {
update_change_status_button();
});
$('button.change-status').click(function() {
var uuids = [];
$('.grid tr:not(.header) td.checkbox input:checked').each(function() {
uuids.push($(this).parents('tr:first').data('uuid'));
});
if (! uuids.length) {
alert("You must first select one or more credits.");
return false;
}
var form = $('form[name="change-status"]');
form.find('[name="uuids"]').val(uuids.toString());
$('#change-status-dialog').dialog({
title: "Change Credit Status",
width: 500,
height: 300,
modal: true,
open: function() {
// TODO: why must we do this here instead of using auto-enhance ?
$('#change-status-dialog select[name="status"]').selectmenu();
},
buttons: [
{
text: "Submit",
click: function(event) {
disable_button(dialog_button(event));
form.submit();
}
},
{
text: "Cancel",
click: function() {
$(this).dialog('close');
}
}
]
});
});
});
</script>
</%def>
<%def name="grid_tools()">
${parent.grid_tools()}
<button type="button" class="change-status" disabled="disabled">Change Status</button>
</%def>
${parent.body()}
<div id="change-status-dialog" style="display: none;">
${h.form(url('purchases.credits.change_status'), name='change-status')}
${h.csrf_token(request)}
${h.hidden('uuids')}
<br />
<p>Please choose the appropriate status for the selected credits.</p>
<div class="fieldset">
<div class="field-wrapper status">
<label for="status">Status</label>
<div class="field">
${h.select('status', None, status_options)}
</div>
</div>
</div>
${h.end_form()}
</div>

View file

@ -1,102 +1,13 @@
## -*- coding: utf-8 -*-
<%inherit file="/base.mako" />
<%namespace file="/autocomplete.mako" import="autocomplete" />
## -*- coding: utf-8; -*-
<%inherit file="/page.mako" />
<%def name="title()">${page_title}</%def>
<%def name="extra_javascript()">
${parent.extra_javascript()}
<script type="text/javascript">
var data_modified = false;
var okay_to_leave = true;
var previous_selections = {};
% if weekdays is not Undefined:
var weekdays = [
% for i, day in enumerate(weekdays, 1):
'${day.strftime('%a %d %b %Y')}'${',' if i < len(weekdays) else ''}
% endfor
];
% endif
window.onbeforeunload = function() {
if (! okay_to_leave) {
return "If you leave this page, you will lose all unsaved changes!";
}
}
function employee_selected(uuid, name) {
$('#filter-form').submit();
}
function confirm_leave() {
if (data_modified) {
if (confirm("If you navigate away from this page now, you will lose " +
"unsaved changes.\n\nAre you sure you wish to do this?")) {
okay_to_leave = true;
return true;
}
return false;
}
return true;
}
function date_selected(dateText, inst) {
if (confirm_leave()) {
$('#filter-form').submit();
} else {
// revert date value
$('.week-picker input[name="date"]').val($('.week-picker').data('week'));
}
}
$(function() {
$('#filter-form').submit(function() {
$('.timesheet-header').mask("Fetching data");
});
$('.timesheet-header select').each(function() {
previous_selections[$(this).attr('name')] = $(this).val();
});
$('.timesheet-header select').selectmenu({
change: function(event, ui) {
if (confirm_leave()) {
$('#filter-form').submit();
} else {
var select = ui.item.element.parents('select');
select.val(previous_selections[select.attr('name')]);
select.selectmenu('refresh');
}
}
});
$('.timesheet-header a.goto').click(function() {
$('.timesheet-header').mask("Fetching data");
});
$('.week-picker button.nav').click(function() {
if (confirm_leave()) {
$('.week-picker input[name="date"]').val($(this).data('date'));
$('#filter-form').submit();
}
});
});
</script>
</%def>
<%def name="extra_styles()">
${parent.extra_styles()}
${h.stylesheet_link(request.static_url('tailbone:static/css/timesheet.css'))}
</%def>
<%def name="edit_timetable_javascript()">
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.edit-shifts.js'))}
</%def>
<%def name="edit_timetable_styles()">
<style type="text/css">
.timesheet .day {
@ -304,5 +215,9 @@
<%def name="render_extra_totals(employee)"></%def>
<%def name="page_content()">
${self.timesheet_wrapper()}
</%def>
${self.timesheet_wrapper()}
${parent.body()}

View file

@ -1,60 +1,6 @@
## -*- coding: utf-8 -*-
## -*- coding: utf-8; -*-
<%inherit file="/shifts/base.mako" />
<%def name="extra_javascript()">
${parent.extra_javascript()}
${self.edit_timetable_javascript()}
<script type="text/javascript">
$(function() {
% if allow_clear:
$('.clear-schedule').click(function() {
if (confirm("This will remove all shifts from the schedule you're " +
"currently viewing.\n\nAre you sure you wish to do this?")) {
$(this).button('disable').button('option', 'label', "Clearing...");
okay_to_leave = true;
$('#clear-schedule-form').submit();
}
});
% endif
$('#copy-week').datepicker({
dateFormat: 'mm/dd/yy'
});
$('.copy-schedule').click(function() {
$('#copy-details').dialog({
modal: true,
title: "Copy from Another Week",
width: '500px',
buttons: [
{
text: "Copy Schedule",
click: function(event) {
if (! $('#copy-week').val()) {
alert("You must specify the week from which to copy shift data.");
$('#copy-week').focus();
return;
}
disable_button(dialog_button(event), "Copying Schedule");
$('#copy-schedule-form').submit();
}
},
{
text: "Cancel",
click: function() {
$('#copy-details').dialog('close');
}
}
]
});
});
});
</script>
</%def>
<%def name="extra_styles()">
${parent.extra_styles()}
${self.edit_timetable_styles()}
@ -97,43 +43,50 @@
</div>
</%def>
<%def name="page_content()">
${self.timesheet_wrapper(with_edit_form=True)}
${self.timesheet_wrapper(with_edit_form=True)}
${edit_tools()}
${edit_tools()}
% if allow_clear:
${h.form(url('schedule.edit'), id="clear-schedule-form")}
${h.csrf_token(request)}
${h.hidden('clear-schedule', value='clear')}
${h.end_form()}
% endif
<div id="day-editor" style="display: none;">
<div class="shifts"></div>
<button type="button" id="add-shift">Add Shift</button>
</div>
<div id="copy-details" style="display: none;">
<p>
This tool will replace the currently visible schedule, with one from
another week.
</p>
<p>
<strong>NOTE:</strong>&nbsp; If you do this, all shifts in the current
schedule will be <em>removed</em>,
and then new shifts will be created based on the week you specify.
</p>
${h.form(url('schedule.edit'), id='copy-schedule-form')}
% if allow_clear:
${h.form(url('schedule.edit'), id="clear-schedule-form")}
${h.csrf_token(request)}
<label for="copy-week">Copy from week:</label>
${h.text('copy-week')}
${h.hidden('clear-schedule', value='clear')}
${h.end_form()}
</div>
% endif
<div id="snippets">
<div class="shift" data-uuid="">
${h.text('edit_start_time')} thru ${h.text('edit_end_time')}
<button type="button"><span class="ui-icon ui-icon-trash"></span></button>
<div id="day-editor" style="display: none;">
<div class="shifts"></div>
<button type="button" id="add-shift">Add Shift</button>
</div>
</div>
<div id="copy-details" style="display: none;">
<p>
This tool will replace the currently visible schedule, with one from
another week.
</p>
<p>
<strong>NOTE:</strong>&nbsp; If you do this, all shifts in the current
schedule will be <em>removed</em>,
and then new shifts will be created based on the week you specify.
</p>
${h.form(url('schedule.edit'), id='copy-schedule-form')}
${h.csrf_token(request)}
<label for="copy-week">Copy from week:</label>
${h.text('copy-week')}
${h.end_form()}
</div>
<div id="snippets">
<div class="shift" data-uuid="">
${h.text('edit_start_time')} thru ${h.text('edit_end_time')}
<button type="button"><span class="ui-icon ui-icon-trash"></span></button>
</div>
</div>
</%def>
${parent.body()}

View file

@ -1,4 +1,4 @@
## -*- coding: utf-8 -*-
## -*- coding: utf-8; -*-
<%inherit file="/shifts/base.mako" />
<%def name="context_menu()">
@ -25,4 +25,4 @@
</%def>
${self.timesheet_wrapper()}
${parent.body()}

View file

@ -1,58 +1,6 @@
## -*- coding: utf-8 -*-
## -*- coding: utf-8; -*-
<%inherit file="/shifts/base.mako" />
<%def name="extra_javascript()">
${parent.extra_javascript()}
${h.javascript_link(request.static_url('tailbone:static/js/tailbone.timesheet.edit.js'))}
<script type="text/javascript">
show_timepicker = false;
$(function() {
$('.timesheet').on('click', '.day', function() {
editing_day = $(this);
var editor = $('#day-editor');
var employee = editing_day.siblings('.employee').text();
var date = weekdays[editing_day.get(0).cellIndex - 1];
var shifts = editor.children('.shifts');
shifts.empty();
editing_day.children('.shift:not(.deleted)').each(function() {
var uuid = $(this).data('uuid');
var times = $.trim($(this).children('span').text()).split(' - ');
times[0] = times[0] == '??' ? '' : times[0];
times[1] = times[1] == '??' ? '' : times[1];
add_shift(false, uuid, times[0], times[1]);
});
if (! shifts.children('.shift').length) {
add_shift();
}
editor.dialog({
modal: true,
title: employee + ' - ' + date,
position: {my: 'center', at: 'center', of: editing_day},
width: 'auto',
autoResize: true,
buttons: [
{
text: "Save Changes",
click: save_dialog
},
{
text: "Cancel",
click: function() {
editor.dialog('close');
}
}
]
});
});
});
</script>
</%def>
<%def name="extra_styles()">
${parent.extra_styles()}
${self.edit_timetable_styles()}
@ -88,17 +36,23 @@
${h.csrf_token(request)}
</%def>
<%def name="page_content()">
${self.timesheet_wrapper(with_edit_form=True, change_employee='confirm_leave')}
${self.timesheet_wrapper(with_edit_form=True, change_employee='confirm_leave')}
<div id="day-editor" style="display: none;">
<div class="shifts"></div>
<button type="button" id="add-shift">Add Shift</button>
</div>
<div id="snippets">
<div class="shift" data-uuid="">
${h.text('edit_start_time')} thru ${h.text('edit_end_time')}
<button type="button"><span class="ui-icon ui-icon-trash"></span></button>
<div id="day-editor" style="display: none;">
<div class="shifts"></div>
<button type="button" id="add-shift">Add Shift</button>
</div>
</div>
<div id="snippets">
<div class="shift" data-uuid="">
${h.text('edit_start_time')} thru ${h.text('edit_end_time')}
<button type="button"><span class="ui-icon ui-icon-trash"></span></button>
</div>
</div>
</%def>
${parent.body()}

View file

@ -1,17 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/master/create.mako" />
<%def name="extra_javascript()">
${parent.extra_javascript()}
<script type="text/javascript">
$(function() {
$('.field-wrapper.client_uuid select').selectmenu();
$('.field-wrapper.appliance_type select').selectmenu();
});
</script>
</%def>
${parent.body()}

View file

@ -1,17 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/master/edit.mako" />
<%def name="head_tags()">
${parent.head_tags()}
<script type="text/javascript">
$(function() {
$('.field-wrapper.client_uuid select').selectmenu();
$('.field-wrapper.appliance_type select').selectmenu();
});
</script>
</%def>
${parent.body()}

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2022 Lance Edgar
# Copyright © 2010-2023 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,10 +24,6 @@
Views for "true" purchase credits
"""
from __future__ import unicode_literals, absolute_import
import six
from rattail.db import model
from webhelpers2.html import tags
@ -112,7 +108,7 @@ class PurchaseCreditView(MasterView):
g.filters['status'].default_active = True
g.filters['status'].default_verb = 'not_equal'
# TODO: should not have to convert value to string!
g.filters['status'].default_value = six.text_type(self.enum.PURCHASE_CREDIT_STATUS_SATISFIED)
g.filters['status'].default_value = str(self.enum.PURCHASE_CREDIT_STATUS_SATISFIED)
# g.set_type('upc', 'gpc')
g.set_type('cases_shorted', 'quantity')
@ -175,7 +171,9 @@ class PurchaseCreditView(MasterView):
def status_options(self):
options = []
for value in sorted(self.enum.PURCHASE_CREDIT_STATUS):
options.append(tags.Option(self.enum.PURCHASE_CREDIT_STATUS[value], value))
options.append({
'value': value,
'label': self.enum.PURCHASE_CREDIT_STATUS[value]})
return options
@classmethod