Add speed bump when leaving timesheet page w/ unsaved changes

Also add save/undo buttons to top as well as bottom of timesheet.
This commit is contained in:
Lance Edgar 2016-10-14 13:57:57 -05:00
parent e153390c15
commit 9e7cb532c8
4 changed files with 127 additions and 57 deletions

View file

@ -20,6 +20,10 @@
width: auto; width: auto;
} }
.timesheet-header td.filters .buttons {
margin-bottom: 0;
}
.timesheet-header td.menu { .timesheet-header td.menu {
padding: 0.5em; padding: 0.5em;
vertical-align: top; vertical-align: top;
@ -46,7 +50,7 @@
border-bottom: 1px solid black; border-bottom: 1px solid black;
border-right: 1px solid black; border-right: 1px solid black;
clear: both; clear: both;
margin-top: 0.3em; margin: 0.3em 0;
width: 100%; width: 100%;
} }
@ -82,3 +86,11 @@
.timesheet tbody tr.total { .timesheet tbody tr.total {
font-weight: bold; font-weight: bold;
} }
/******************************
* below timesheet...
******************************/
div.buttons {
margin-top: 0;
}

View file

@ -9,19 +9,51 @@
${h.stylesheet_link(request.static_url('tailbone:static/css/timesheet.css'))} ${h.stylesheet_link(request.static_url('tailbone:static/css/timesheet.css'))}
<script type="text/javascript"> <script type="text/javascript">
var data_modified = false;
var okay_to_leave = true;
var previous_selections = {};
window.onbeforeunload = function() {
if (! okay_to_leave) {
return "If you leave this page, you will lose all unsaved changes!";
}
}
function employee_selected(uuid, name) { function employee_selected(uuid, name) {
$('.timesheet-wrapper form').submit(); $('#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() { $(function() {
$('.timesheet-wrapper form').submit(function() { $('#filter-form').submit(function() {
$('.timesheet-header').mask("Fetching data"); $('.timesheet-header').mask("Fetching data");
}); });
$('.timesheet-header select').each(function() {
previous_selections[$(this).attr('name')] = $(this).val();
});
$('.timesheet-header select').selectmenu({ $('.timesheet-header select').selectmenu({
change: function(event, ui) { change: function(event, ui) {
$(ui.item.element).parents('form').submit(); if (confirm_leave()) {
$('#filter-form').submit();
} else {
var select = ui.item.element.parents('select');
select.val(previous_selections[select.attr('name')]);
select.selectmenu('refresh');
}
} }
}); });
@ -30,7 +62,10 @@
}); });
$('.week-picker button.nav').click(function() { $('.week-picker button.nav').click(function() {
$('.week-picker #date').val($(this).data('date')); if (confirm_leave()) {
$('.week-picker #date').val($(this).data('date'));
$('#filter-form').submit();
}
}); });
$('.week-picker #date').datepicker({ $('.week-picker #date').datepicker({
@ -39,7 +74,7 @@
changeMonth: true, changeMonth: true,
showButtonPanel: true, showButtonPanel: true,
onSelect: function(dateText, inst) { onSelect: function(dateText, inst) {
$(this).parents('form').submit(); $('#filter-form').submit();
} }
}); });
@ -50,7 +85,7 @@
<%def name="context_menu()"></%def> <%def name="context_menu()"></%def>
<%def name="timesheet()"> <%def name="timesheet(edit_form=None, edit_tools=None)">
<style type="text/css"> <style type="text/css">
.timesheet thead th { .timesheet thead th {
width: ${'{:0.2f}'.format(100.0 / 9)}%; width: ${'{:0.2f}'.format(100.0 / 9)}%;
@ -59,7 +94,7 @@
<div class="timesheet-wrapper"> <div class="timesheet-wrapper">
${form.begin()} ${form.begin(id='filter-form')}
<table class="timesheet-header"> <table class="timesheet-header">
<tbody> <tbody>
@ -99,6 +134,10 @@
</div> </div>
</div> </div>
% if edit_tools:
${edit_tools()}
% endif
</td><!-- filters --> </td><!-- filters -->
<td class="menu"> <td class="menu">
@ -112,8 +151,8 @@
<td class="tools"> <td class="tools">
<div class="grid-tools"> <div class="grid-tools">
<div class="week-picker"> <div class="week-picker">
<button class="nav" data-date="${prev_sunday.strftime('%m/%d/%Y')}">&laquo; Previous</button> <button type="button" class="nav" data-date="${prev_sunday.strftime('%m/%d/%Y')}">&laquo; Previous</button>
<button class="nav" data-date="${next_sunday.strftime('%m/%d/%Y')}">Next &raquo;</button> <button type="button" class="nav" data-date="${next_sunday.strftime('%m/%d/%Y')}">Next &raquo;</button>
<label>Jump to week:</label> <label>Jump to week:</label>
${form.text('date', value=sunday.strftime('%m/%d/%Y'))} ${form.text('date', value=sunday.strftime('%m/%d/%Y'))}
</div> </div>
@ -126,6 +165,10 @@
${form.end()} ${form.end()}
% if edit_form:
${edit_form()}
% endif
<table class="timesheet"> <table class="timesheet">
<thead> <thead>
<tr> <tr>
@ -167,6 +210,11 @@
% endif % endif
</tbody> </tbody>
</table> </table>
% if edit_form:
${h.end_form()}
% endif
</div><!-- timesheet-wrapper --> </div><!-- timesheet-wrapper -->
</%def> </%def>

View file

@ -152,10 +152,12 @@
// mark day as modified, close dialog // mark day as modified, close dialog
editing_day.addClass('modified'); editing_day.addClass('modified');
$('#save-changes').button('enable'); $('.save-changes').button('enable');
$('#undo-changes').button('enable'); $('.undo-changes').button('enable');
update_row_hours(editing_day.parents('tr:first')); update_row_hours(editing_day.parents('tr:first'));
editor.dialog('close'); editor.dialog('close');
data_modified = true;
okay_to_leave = false;
} }
}, },
{ {
@ -176,13 +178,15 @@
$(this).parents('.shift:first').remove(); $(this).parents('.shift:first').remove();
}); });
$('#save-changes').click(function() { $('.save-changes').click(function() {
$(this).button('disable').button('option', 'label', "Saving Changes..."); $(this).button('disable').button('option', 'label', "Saving Changes...");
okay_to_leave = true;
$('#schedule-form').submit(); $('#schedule-form').submit();
}); });
$('#undo-changes').click(function() { $('.undo-changes').click(function() {
$(this).button('disable').button('option', 'label', "Refreshing..."); $(this).button('disable').button('option', 'label', "Refreshing...");
okay_to_leave = true;
location.href = '${url('schedule.edit')}'; location.href = '${url('schedule.edit')}';
}); });
@ -232,14 +236,20 @@
% endfor % endfor
</%def> </%def>
${h.form(url('schedule.edit'), id="schedule-form")} <%def name="edit_tools()">
${self.timesheet()} <div class="buttons">
${h.end_form()} <button type="button" class="save-changes" disabled="disabled">Save Changes</button>
<button type="button" class="undo-changes" disabled="disabled">Undo Changes</button>
</div>
</%def>
<div class="buttons"> <%def name="edit_form()">
<button type="button" id="save-changes" disabled="disabled">Save Changes</button> ${h.form(url('schedule.edit'), id='schedule-form')}
<button type="button" id="undo-changes" disabled="disabled">Undo Changes</button> </%def>
</div>
${self.timesheet(edit_form=edit_form, edit_tools=edit_tools)}
${edit_tools()}
<div id="day-editor" style="display: none;"> <div id="day-editor" style="display: none;">
<div class="shifts"></div> <div class="shifts"></div>

View file

@ -48,6 +48,11 @@ class ScheduleView(TimeSheetView):
""" """
View for editing (full) schedule. View for editing (full) schedule.
""" """
# first process filters; will redirect if any were received
form = Form(self.request, schema=ShiftFilter)
self.process_filter_form(form)
# okay then, maybe process saved shift data
if self.request.method == 'POST': if self.request.method == 'POST':
# organize form data by uuid / field # organize form data by uuid / field
@ -61,47 +66,42 @@ class ScheduleView(TimeSheetView):
data[field][uuid] = self.request.POST[key] data[field][uuid] = self.request.POST[key]
break break
# if no fields, don't apply changes (filter form uses POST also) # apply delete operations
if any(data.itervalues()): deleted = []
for uuid, value in data['delete'].iteritems():
assert value == 'delete'
shift = Session.query(model.ScheduledShift).get(uuid)
assert shift
Session.delete(shift)
deleted.append(uuid)
# apply delete operations # apply create / update operations
deleted = [] created = {}
for uuid, value in data['delete'].iteritems(): updated = {}
assert value == 'delete' time_format = '%a %d %b %Y %I:%M %p'
for uuid, employee_uuid in data['start_time'].iteritems():
if uuid in deleted:
continue
if uuid.startswith('new-'):
shift = model.ScheduledShift()
shift.employee_uuid = data['employee_uuid'][uuid]
shift.store_uuid = data['store_uuid'][uuid]
Session.add(shift)
created[uuid] = shift
else:
shift = Session.query(model.ScheduledShift).get(uuid) shift = Session.query(model.ScheduledShift).get(uuid)
assert shift assert shift
Session.delete(shift) updated[uuid] = shift
deleted.append(uuid) start_time = datetime.datetime.strptime(data['start_time'][uuid], time_format)
shift.start_time = make_utc(localtime(self.rattail_config, start_time))
end_time = datetime.datetime.strptime(data['end_time'][uuid], time_format)
shift.end_time = make_utc(localtime(self.rattail_config, end_time))
# apply create / update operations self.request.session.flash("Changes were applied: created {}, updated {}, "
created = {} "deleted {} Scheduled Shifts".format(
updated = {} len(created), len(updated), len(deleted)))
time_format = '%a %d %b %Y %I:%M %p' return self.redirect(self.request.route_url('schedule.edit'))
for uuid, employee_uuid in data['start_time'].iteritems():
if uuid in deleted:
continue
if uuid.startswith('new-'):
shift = model.ScheduledShift()
shift.employee_uuid = data['employee_uuid'][uuid]
shift.store_uuid = data['store_uuid'][uuid]
Session.add(shift)
created[uuid] = shift
else:
shift = Session.query(model.ScheduledShift).get(uuid)
assert shift
updated[uuid] = shift
start_time = datetime.datetime.strptime(data['start_time'][uuid], time_format)
shift.start_time = make_utc(localtime(self.rattail_config, start_time))
end_time = datetime.datetime.strptime(data['end_time'][uuid], time_format)
shift.end_time = make_utc(localtime(self.rattail_config, end_time))
self.request.session.flash("Changes were applied: created {}, updated {}, "
"deleted {} Scheduled Shifts".format(
len(created), len(updated), len(deleted)))
return self.redirect(self.request.route_url('schedule.edit'))
form = Form(self.request, schema=ShiftFilter)
self.process_filter_form(form)
context = self.get_timesheet_context() context = self.get_timesheet_context()
context['form'] = form context['form'] = form
context['page_title'] = "Edit Schedule" context['page_title'] = "Edit Schedule"