diff --git a/tailbone/templates/shifts/base.mako b/tailbone/templates/shifts/base.mako
index c5427547..c00460d2 100644
--- a/tailbone/templates/shifts/base.mako
+++ b/tailbone/templates/shifts/base.mako
@@ -138,16 +138,14 @@
         
         
           % for emp in sorted(employees, key=unicode):
-              
+              
                 | ${emp} | 
                 % for day in emp.weekdays:
-                    
-                      % for shift in day['shifts']:
-                           ${render_shift(shift)} 
-                      % endfor
+                     | 
+                      ${self.render_day(day)}
                      | 
                 % endfor
-                ${emp.hours_display} | 
+                ${emp.hours_display} | 
               
           % endfor
           % if employee is UNDEFINED:
@@ -171,3 +169,9 @@
       
     
 %def>
+
+<%def name="render_day(day)">
+  % for shift in day['shifts']:
+      ${render_shift(shift)}
+  % endfor
+%def>
diff --git a/tailbone/templates/shifts/schedule.mako b/tailbone/templates/shifts/schedule.mako
index 420c2ceb..72c710ed 100644
--- a/tailbone/templates/shifts/schedule.mako
+++ b/tailbone/templates/shifts/schedule.mako
@@ -2,11 +2,12 @@
 <%inherit file="/shifts/base.mako" />
 
 <%def name="context_menu()">
-    % if request.has_perm('timesheet.view'):
-        ${h.link_to("View this Time Sheet", url('schedule.goto.timesheet'), class_='goto')}
-    % endif
-##     ${h.link_to("Print this Schedule", '#')}
-##     ${h.link_to("Edit this Schedule", '#')}
+  % if request.has_perm('schedule.edit'):
+      ${h.link_to("Edit Schedule", url('schedule.edit'))}
+  % endif
+  % if request.has_perm('timesheet.view'):
+      ${h.link_to("View this Time Sheet", url('schedule.goto.timesheet'), class_='goto')}
+  % endif
 %def>
 
 ${self.timesheet()}
diff --git a/tailbone/templates/shifts/schedule_edit.mako b/tailbone/templates/shifts/schedule_edit.mako
new file mode 100644
index 00000000..97e86f92
--- /dev/null
+++ b/tailbone/templates/shifts/schedule_edit.mako
@@ -0,0 +1,254 @@
+## -*- coding: utf-8 -*-
+<%inherit file="/shifts/base.mako" />
+
+<%def name="head_tags()">
+  ${parent.head_tags()}
+  
+  
+%def>
+
+<%def name="context_menu()">
+  % if request.has_perm('schedule.viewall'):
+      ${h.link_to("View Schedule", url('schedule'))}
+  % endif
+%def>
+
+<%def name="render_day(day)">
+  % for shift in day['shifts']:
+      
+        ${render_shift(shift)}
+      
+  % endfor
+%def>
+
+${h.form(url('schedule.edit'), id="schedule-form")}
+${self.timesheet()}
+${h.end_form()}
+
+
+  
+  
+
+
+
+
+
+  
+    ${h.text('edit_start_time')} thru ${h.text('edit_end_time')}
+    
+  
+
 
diff --git a/tailbone/views/shifts/core.py b/tailbone/views/shifts/core.py
index 2023e871..4335c42e 100644
--- a/tailbone/views/shifts/core.py
+++ b/tailbone/views/shifts/core.py
@@ -59,6 +59,11 @@ class ScheduledShiftsView(MasterView):
     url_prefix = '/shifts/scheduled'
 
     def configure_grid(self, g):
+        g.joiners['employee'] = lambda q: q.join(model.Employee).join(model.Person)
+        g.filters['employee'] = g.make_filter('employee', model.Person.display_name,
+                                              default_active=True, default_verb='contains',
+                                              label="Employee Name")
+
         g.default_sortkey = 'start_time'
         g.default_sortdir = 'desc'
         g.append(ShiftLengthField('length'))
diff --git a/tailbone/views/shifts/lib.py b/tailbone/views/shifts/lib.py
index 3cb8b0ef..c6e07362 100644
--- a/tailbone/views/shifts/lib.py
+++ b/tailbone/views/shifts/lib.py
@@ -34,6 +34,7 @@ from rattail.time import localtime, make_utc, get_sunday
 
 import formencode as fe
 from pyramid_simpleform import Form
+from webhelpers.html import HTML
 
 from tailbone import forms
 from tailbone.db import Session
@@ -71,14 +72,60 @@ class TimeSheetView(View):
     def get_title(cls):
         return cls.title or cls.key.capitalize()
 
-    def full(self):
+    def get_timesheet_context(self):
+        """
+        Determine date/store/dept context from user's session and/or defaults.
+        """
         date = None
+        date_key = 'timesheet.{}.date'.format(self.key)
+        if date_key in self.request.session:
+            date_value = self.request.session.get(date_key)
+            if date_value:
+                try:
+                    date = datetime.datetime.strptime(date_value, '%m/%d/%Y').date()
+                except ValueError:
+                    pass
+        if not date:
+            date = localtime(self.rattail_config).date()
+
         store = None
         department = None
+        store_key = 'timesheet.{}.store'.format(self.key)
+        department_key = 'timesheet.{}.department'.format(self.key)
+        if store_key in self.request.session or department_key in self.request.session:
+            store_uuid = self.request.session.get(store_key)
+            if store_uuid:
+                store = Session.query(model.Store).get(store_uuid) if store_uuid else None
+            department_uuid = self.request.session.get(department_key)
+            if department_uuid:
+                department = Session.query(model.Department).get(department_uuid)
+        else: # no store/department in session
+            if self.default_filter_store:
+                store = self.rattail_config.get('rattail', 'store')
+                if store:
+                    store = api.get_store(Session(), store)
+
         employees = Session.query(model.Employee)\
                            .filter(model.Employee.status == enum.EMPLOYEE_STATUS_CURRENT)
+        if store:
+            employees = employees.join(model.EmployeeStore)\
+                                 .filter(model.EmployeeStore.store == store)
+        if department:
+            employees = employees.join(model.EmployeeDepartment)\
+                                 .filter(model.EmployeeDepartment.department == department)
 
-        form = Form(self.request, schema=ShiftFilter)
+        return {
+            'date': date,
+            'store': store,
+            'department': department,
+            'employees': employees.all(),
+        }
+
+    def process_filter_form(self, form):
+        """
+        Process a "shift filter" form if one was in fact POST'ed.  If it was
+        then we store new context in session and redirect to display as normal.
+        """
         if self.request.method == 'POST':
             if form.validate():
                 store = form.data['store']
@@ -87,45 +134,18 @@ class TimeSheetView(View):
                 self.request.session['timesheet.{}.department'.format(self.key)] = department.uuid if department else None
                 date = form.data['date']
                 self.request.session['timesheet.{}.date'.format(self.key)] = date.strftime('%m/%d/%Y') if date else None
-                return self.redirect(self.request.current_route_url())
+                raise self.redirect(self.request.current_route_url())
 
-        else:
-            store_key = 'timesheet.{}.store'.format(self.key)
-            department_key = 'timesheet.{}.department'.format(self.key)
-            if store_key in self.request.session or department_key in self.request.session:
-                store_uuid = self.request.session.get(store_key)
-                if store_uuid:
-                    store = Session.query(model.Store).get(store_uuid) if store_uuid else None
-                department_uuid = self.request.session.get(department_key)
-                if department_uuid:
-                    department = Session.query(model.Department).get(department_uuid)
-            else: # no store/department in session
-                if self.default_filter_store:
-                    store = self.rattail_config.get('rattail', 'store')
-                    if store:
-                        store = api.get_store(Session(), store)
-
-            date_key = 'timesheet.{}.date'.format(self.key)
-            if date_key in self.request.session:
-                date_value = self.request.session.get(date_key)
-                if date_value:
-                    try:
-                        date = datetime.datetime.strptime(date_value, '%m/%d/%Y').date()
-                    except ValueError:
-                        pass
-
-        if store:
-            employees = employees.join(model.EmployeeStore)\
-                                 .filter(model.EmployeeStore.store == store)
-
-        if department:
-            employees = employees.join(model.EmployeeDepartment)\
-                                 .filter(model.EmployeeDepartment.department == department)
-
-        if not date:
-            date = localtime(self.rattail_config).date()
-
-        return self.render_full(date, employees.all(), store=store, department=department, form=form)
+    def full(self):
+        """
+        View a "full" timesheet/schedule, i.e. all employees but filterable by
+        store and/or department.
+        """
+        form = Form(self.request, schema=ShiftFilter)
+        self.process_filter_form(form)
+        context = self.get_timesheet_context()
+        context['form'] = form
+        return self.render_full(**context)
 
     def employee(self):
         """
@@ -233,7 +253,7 @@ class TimeSheetView(View):
         options.insert(0, ('', "(all)"))
         return options
 
-    def render_full(self, date, employees, store=None, department=None, form=None):
+    def render_full(self, date=None, employees=None, store=None, department=None, form=None, **kwargs):
         """
         Render a time sheet for one or more employees, for the week which
         includes the specified date.
@@ -257,7 +277,7 @@ class TimeSheetView(View):
         departments = self.get_departments()
         department_options = self.get_department_options(departments)
 
-        return {
+        context = {
             'page_title': "Full {}".format(self.get_title()),
             'form': forms.FormRenderer(form) if form else None,
             'employees': employees,
@@ -275,9 +295,11 @@ class TimeSheetView(View):
             'permission_prefix': self.key,
             'render_shift': self.render_shift,
         }
+        context.update(kwargs)
+        return context
 
     def render_shift(self, shift):
-        return shift.get_display(self.rattail_config)
+        return HTML.tag('span', c=shift.get_display(self.rattail_config))
 
     def render_single(self, date, employee, form=None):
         """
@@ -371,7 +393,7 @@ class TimeSheetView(View):
         """
         title = cls.get_title()
         config.add_tailbone_permission_group(cls.key, title)
-        config.add_tailbone_permission(cls.key, '{}.view'.format(cls.key), "View employee {}".format(title))
+        config.add_tailbone_permission(cls.key, '{}.view'.format(cls.key), "View single employee {}".format(title))
         config.add_tailbone_permission(cls.key, '{}.viewall'.format(cls.key), "View full {}".format(title))
 
         # full time sheet
diff --git a/tailbone/views/shifts/schedule.py b/tailbone/views/shifts/schedule.py
index 6229fbd2..21952fbc 100644
--- a/tailbone/views/shifts/schedule.py
+++ b/tailbone/views/shifts/schedule.py
@@ -26,9 +26,15 @@ Views for employee schedules
 
 from __future__ import unicode_literals, absolute_import
 
-from rattail.db import model
+import datetime
 
-from tailbone.views.shifts.lib import TimeSheetView
+from rattail.db import model
+from rattail.time import localtime, make_utc
+
+from pyramid_simpleform import Form
+
+from tailbone.db import Session
+from tailbone.views.shifts.lib import TimeSheetView, ShiftFilter
 
 
 class ScheduleView(TimeSheetView):
@@ -38,6 +44,76 @@ class ScheduleView(TimeSheetView):
     key = 'schedule'
     model_class = model.ScheduledShift
 
+    def edit(self):
+        """
+        View for editing (full) schedule.
+        """
+        if self.request.method == 'POST':
+
+            # organize form data by uuid / field
+            fields = ['employee_uuid', 'store_uuid', 'start_time', 'end_time', 'delete']
+            data = dict([(f, {}) for f in fields])
+            for key in self.request.POST:
+                for field in fields:
+                    if key.startswith('{}-'.format(field)):
+                        uuid = key[len('{}-'.format(field)):]
+                        if uuid:
+                            data[field][uuid] = self.request.POST[key]
+
+            # apply delete operations
+            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 create / update operations
+            created = {}
+            updated = {}
+            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)
+                    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['form'] = form
+        context['page_title'] = "Edit Schedule"
+        return self.render_full(**context)
+
+    @classmethod
+    def defaults(cls, config):
+        cls._defaults(config)
+
+        # edit schedule
+        config.add_route('schedule.edit', '/schedule/edit')
+        config.add_view(cls, attr='edit', route_name='schedule.edit',
+                        renderer='/shifts/schedule_edit.mako',
+                        permission='schedule.edit')
+        config.add_tailbone_permission('schedule', 'schedule.edit', "Edit full schedule")
+
 
 def includeme(config):
     ScheduleView.defaults(config)