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:
parent
e153390c15
commit
9e7cb532c8
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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')}">« Previous</button>
|
<button type="button" class="nav" data-date="${prev_sunday.strftime('%m/%d/%Y')}">« Previous</button>
|
||||||
<button class="nav" data-date="${next_sunday.strftime('%m/%d/%Y')}">Next »</button>
|
<button type="button" class="nav" data-date="${next_sunday.strftime('%m/%d/%Y')}">Next »</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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in a new issue