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
					
				
					 4 changed files with 127 additions and 57 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lance Edgar
						Lance Edgar